Aller-retour types de numéros Swift vers / à partir de données

Avec Swift 3 orienté vers Data au lieu de [UInt8] , j’essaie de trouver le moyen le plus efficace / idiomatique d’encoder / décoder les différents types de nombres (UInt8, Double, Float, Int64, etc.) en tant qu’objects Data.

Il y a cette réponse pour utiliser [UInt8] , mais il semble utiliser diverses API de pointeur que je ne trouve pas sur Data.

Je voudrais essentiellement des extensions personnalisées qui ressemblent à:

 let input = 42.13 // implicit Double let bytes = input.data let roundsortingp = bytes.to(Double) // --> 42.13 

La partie qui m’échappe vraiment, j’ai regardé à travers un tas de documents, est comment je peux obtenir une sorte de pointeur (OpaquePointer ou BufferPointer ou UnsafePointer?) De n’importe quelle structure de base (que tous les nombres sont). En C, je me contenterais de bash une esperluète devant elle et voilà.

Comment créer des Data partir d’une valeur

struct Data a un initialiseur

 public init(bytes: UnsafeRawPointer, count: Int) 

qui peut être utilisé de la même manière que dans les différentes réponses à la question Comment convertir un double en un tableau d’octets dans swift? que vous avez lié à:

 let input = 42.13 var value = input let data = withUnsafePointer(to: &value) { Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: input)) } print(data as NSData) // <713d0ad7 a3104540> 

Comme @zneak l’a déjà dit, vous ne pouvez prendre l’adresse que d’une variable . Par conséquent, une copie de variable est faite avec var value = value . Dans les versions précédentes de Swift, vous pouviez y parvenir en faisant du paramètre de la fonction lui-même une variable, qui n’est plus prise en charge.

Cependant, il est plus facile d’utiliser l’initialiseur

 public init(buffer: UnsafeBufferPointer) 

au lieu:

 let input = 42.13 var value = input let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) print(data as NSData) // <713d0ad7 a3104540> 

Notez que l’espace réservé générique SourceType est automatiquement déduit du contexte.

Comment récupérer une valeur à partir de Data

NSData possédait une propriété d’ bytes pour accéder au stockage sous-jacent. struct Data a un générique

 public func withUnsafeBytes(_ body: @noescape (UnsafePointer) throws -> ResultType) rethrows -> ResultType 

à la place, qui peut être utilisé comme ceci:

 let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value = data.withUnsafeBytes { (ptr: UnsafePointer) -> Double in return ptr.pointee } print(value) // 42.13 

Si le ContentType peut être déduit du contexte, il n’est pas nécessaire de le spécifier dans la fermeture, cela peut donc être simplifié pour

 let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value: Double = data.withUnsafeBytes { $0.pointee } print(value) // 42.13 

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 :

 extension Data { init(from value: T) { var value = value self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) } func to(type: T.Type) -> T { return self.withUnsafeBytes { $0.pointee } } } 

Exemple:

 let input = 42.13 // implicit Double let data = Data(from: input) print(data as NSData) // <713d0ad7 a3104540> let roundsortingp = data.to(type: Double.self) print(roundsortingp) // 42.13 

De même, vous pouvez convertir des tableaux en Data et inversement:

 extension Data { init(fromArray values: [T]) { var values = values self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count)) } func toArray(type: T.Type) -> [T] { return self.withUnsafeBytes { [T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout.ssortingde)) } } } 

Exemple:

 let input: [Int16] = [1, Int16.max, Int16.min] let data = Data(fromArray: input) print(data as NSData) // <0100ff7f 0080> let roundsortingp = data.toArray(type: Int16.self) print(roundsortingp) // [1, 32767, -32768] 

Solution générique n ° 2

L’approche ci-dessus présente un inconvénient: Comme dans Comment convertir un double en un tableau d’octets dans swift? , il ne fonctionne en fait qu’avec des types “simples” comme les entiers et les types à virgule flottante. Les types “complexes” comme Array et Ssortingng ont des pointeurs (cachés) sur le stockage sous-jacent et ne peuvent pas être contournés simplement en copiant la structure elle-même. Cela ne fonctionnerait pas non plus avec les types de référence qui ne sont que des pointeurs vers le stockage d’objects réels.

Donc, résolvez ce problème, on peut

  • Définir un protocole qui définit les méthodes de conversion en Data et retour:

     protocol DataConvertible { init?(data: Data) var data: Data { get } } 
  • Implémentez les conversions comme méthodes par défaut dans une extension de protocole:

     extension DataConvertible { init?(data: Data) { guard data.count == MemoryLayout.size else { return nil } self = data.withUnsafeBytes { $0.pointee } } var data: Data { var value = self return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } } 

    J’ai choisi un initialiseur disponible ici qui vérifie que le nombre d’octets fournis correspond à la taille du type.

  • Et enfin déclarer la conformité à tous les types pouvant être convertis en toute sécurité en Data et retour:

     extension Int : DataConvertible { } extension Float : DataConvertible { } extension Double : DataConvertible { } // add more types here ... 

Cela rend la conversion encore plus élégante:

 let input = 42.13 let data = input.data print(data as NSData) // <713d0ad7 a3104540> if let roundsortingp = Double(data: data) { print(roundsortingp) // 42.13 } 

L’avantage de la seconde approche est que vous ne pouvez pas faire par inadvertance des conversions dangereuses. 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 nécessitant une conversion non sortingviale, tels que:

 extension Ssortingng: DataConvertible { init?(data: Data) { self.init(data: data, encoding: .utf8) } var data: Data { // Note: a conversion to UTF-8 cannot fail. return self.data(using: .utf8)! } } 

ou implémentez les méthodes de conversion dans vos propres types pour faire le nécessaire afin de sérialiser et désérialiser une valeur.

Vous pouvez obtenir un pointeur non sécurisé sur des objects mutables en utilisant withUnsafePointer :

 withUnsafePointer(&input) { /* $0 is your pointer */ } 

Je ne connais pas de moyen d’en obtenir un pour les objects immuables, car l’opérateur inout ne fonctionne que sur des objects mutables.

Ceci est démontré dans la réponse à laquelle vous avez lié.

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:

 extension UInt16 : DataConvertible { init?(data: Data) { guard data.count == MemoryLayout.size else { return nil } self = data.withUnsafeBytes { $0.pointee } } var data: Data { var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } } 

Le problème est lié à LittleEndian et BigEndian.