Avec Swift 3 penché vers Data
plutôt que vers [UInt8]
, j'essaie de découvrir quelle est la manière la plus efficace / idiomatique d'encoder / décoder divers types de nombres (UInt8, Double, Float, Int64, etc.) en tant qu'objets de données.
Il y a cette réponse pour utiliser [UInt8] , mais il semble utiliser diverses API de pointeur que je ne trouve pas sur Data.
J'aimerais essentiellement des extensions personnalisées qui ressemblent à quelque chose comme:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
La partie qui m'échappe vraiment, j'ai regardé à travers un tas de documents, est de savoir comment je peux obtenir une sorte de pointeur (OpaquePointer ou BufferPointer ou UnsafePointer?) À partir de n'importe quelle structure de base (dont tous les nombres sont). En C, je giflerais juste une esperluette devant elle, et voilà.
la source
Réponses:
Remarque: le code a été mis à jour pour Swift 5 (Xcode 10.2) maintenant. (Les versions Swift 3 et Swift 4.2 peuvent être trouvées dans l'historique des modifications.) Les données éventuellement non alignées sont désormais correctement gérées.
Comment créer à
Data
partir d'une valeurDepuis Swift 4.2, les données peuvent être créées à partir d'une valeur simplement avec
Explication:
withUnsafeBytes(of: value)
invoque la fermeture avec un pointeur de tampon couvrant les octets bruts de la valeur.Data($0)
peut donc être utilisé pour créer les données.Comment récupérer une valeur de
Data
Depuis Swift 5, le
withUnsafeBytes(_:)
ofData
appelle la fermeture avec un «non typé»UnsafeMutableRawBufferPointer
aux octets. Laload(fromByteOffset:as:)
méthode qui lit la valeur de la mémoire:Il y a un problème avec cette approche: elle nécessite que la mémoire soit alignée sur la propriété pour le type (ici: alignée sur une adresse de 8 octets). Mais cela n'est pas garanti, par exemple si les données ont été obtenues sous forme de tranche d'une autre
Data
valeur.Il est donc plus sûr de copier les octets dans la valeur:
Explication:
withUnsafeMutableBytes(of:_:)
invoque la fermeture avec un pointeur de tampon mutable couvrant les octets bruts de la valeur.copyBytes(to:)
méthode deDataProtocol
(à laquelle seData
conforme) copie les octets des données vers ce tampon.La valeur de retour de
copyBytes()
est le nombre d'octets copiés. Il est égal à la taille du tampon de destination, ou moins si les données ne contiennent pas suffisamment d'octets.Solution générique n ° 1
Les conversions ci-dessus peuvent désormais être facilement implémentées en tant que méthodes génériques de
struct Data
:La contrainte
T: ExpressibleByIntegerLiteral
est ajoutée ici pour que nous puissions facilement initialiser la valeur à «zéro» - ce n'est pas vraiment une restriction car cette méthode peut quand même être utilisée avec les types «trival» (entier et virgule flottante), voir ci-dessous.Exemple:
De même, vous pouvez convertir des tableaux en
Data
et inversement:Exemple:
Solution générique # 2
L'approche ci-dessus a un inconvénient: elle ne fonctionne en fait qu'avec des types "triviaux" comme les entiers et les types à virgule flottante. Types "complexes" comme
Array
etString
ont des pointeurs (cachés) vers le stockage sous-jacent et ne peuvent pas être transmis en copiant simplement la structure elle-même. Cela ne fonctionnerait pas non plus avec des types de référence qui ne sont que des pointeurs vers le stockage d'objets réel.Alors résolvez ce problème, on peut
Définissez un protocole qui définit les méthodes de conversion vers
Data
et inversement:Implémentez les conversions comme méthodes par défaut dans une extension de protocole:
Je l' ai choisi un failable initialiseur ici qui vérifie que le nombre d'octets fournis correspond à la taille du type.
Et enfin, déclarez la conformité à tous les types qui peuvent être convertis
Data
et inversés en toute sécurité :Cela rend la conversion encore plus élégante:
L'avantage de la deuxième approche est que vous ne pouvez pas effectuer par inadvertance des conversions non sécurisées. L'inconvénient est que vous devez lister explicitement tous les types "sûrs".
Vous pouvez également implémenter le protocole pour d'autres types qui nécessitent une conversion non triviale, tels que:
ou implémentez les méthodes de conversion dans vos propres types pour faire tout ce qui est nécessaire pour sérialiser et désérialiser une valeur.
Ordre des octets
Aucune conversion d'ordre des octets n'est effectuée dans les méthodes ci-dessus, les données sont toujours dans l'ordre des octets de l'hôte. Pour une représentation indépendante de la plate-forme (par exemple «big endian» aka «network» byte order), utilisez les propriétés entières correspondantes resp. initialiseurs. Par exemple:
Bien entendu, cette conversion peut également se faire de manière générale, dans la méthode de conversion générique.
la source
var
copie de la valeur initiale signifie-t-il que nous copions les octets deux fois? Dans mon cas d'utilisation actuel, je les transforme en structures de données, afin que je puisseappend
les transformer en un flux croissant d'octets. En C droit, c'est aussi simple que*(cPointer + offset) = originalValue
. Ainsi, les octets ne sont copiés qu'une seule fois.ptr: UnsafeMutablePointer<UInt8>
vous pouvez l'assigner à la mémoire référencée via quelque chose commeUnsafeMutablePointer<T>(ptr + offset).pointee = value
qui correspond étroitement à votre code Swift. Il existe un problème potentiel: certains processeurs n'autorisent qu'un accès mémoire aligné , par exemple, vous ne pouvez pas stocker un Int à un emplacement de mémoire impair. Je ne sais pas si cela s'applique aux processeurs Intel et ARM actuellement utilisés.extension Array: DataConvertible where Element: DataConvertible
. Ce n'est pas possible dans Swift 3, mais prévu pour Swift 4 (pour autant que je sache). Comparez les "conformités conditionnelles" sur github.com/apple/swift/blob/master/docs/…Int.self
commeInt.Type
?Vous pouvez obtenir un pointeur non sécurisé vers des objets mutables en utilisant
withUnsafePointer
:Je ne connais pas de moyen d'en obtenir un pour les objets immuables, car l'opérateur inout ne fonctionne que sur les objets mutables.
Ceci est démontré dans la réponse à laquelle vous avez lié.
la source
Dans mon cas, la réponse de Martin R a aidé mais le résultat a été inversé. J'ai donc fait un petit changement dans son code:
Le problème est lié à LittleEndian et BigEndian.
la source