round trip Tipi di numeri rapidi da / verso Dati

Con Swift 3 inclinato verso Data invece di [UInt8] , sto cercando di scoprire quale sia il modo più efficiente / idiomatico per codificare / decodificare i vari tipi di numeri (UInt8, Double, Float, Int64, ecc.) Come oggetti Data.

C’è questa risposta per usare [UInt8] , ma sembra che stia usando varie API puntatore che non riesco a trovare su Data.

Mi piacerebbe fondamentalmente alcune estensioni personalizzate che assomigliano a qualcosa:

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

La parte che mi sfugge davvero, ho esaminato un sacco di documenti, è come posso ottenere una sorta di puntatore (OpaquePointer o BufferPointer o UnsafePointer?) Da qualsiasi struttura di base (che sono tutti i numeri). In C, vorrei solo schiaffeggiare una e commerciale di fronte ad essa, e ci vai.

Come creare Data da un valore

struct Data ha un inizializzatore

 public init(bytes: UnsafeRawPointer, count: Int) 

che può essere usato in modo simile alle varie risposte alla domanda Come convertire un doppio in un array di byte in swift? che hai collegato a:

 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> 

Come già detto @zneak, puoi prendere solo l’indirizzo di una variabile , quindi una copia variabile viene eseguita con var value = value . Nelle versioni precedenti di Swift è ansible ottenere tale risultato rendendo il parametro funzione stesso una variabile, questo non è più supportato.

Tuttavia, è più semplice utilizzare l’inizializzatore

 public init(buffer: UnsafeBufferPointer) 

anziché:

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

Si noti che il segnaposto generico SourceType viene automaticamente dedotto dal contesto.

Come recuperare un valore dai Data

NSData aveva una proprietà bytes per ottenere l’accesso allo storage sottostante. struct Data ha un generico

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

invece, che può essere usato in questo modo:

 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 

Se ContentType può essere dedotto dal contesto, non è necessario specificarlo nella chiusura, quindi è ansible semplificarlo

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

Soluzione generica n. 1

Le conversioni di cui sopra ora possono essere facilmente implementate come metodi generici di 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 } } } 

Esempio:

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

Allo stesso modo, puoi convertire gli array in Data e tornare:

 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.stride)) } } } 

Esempio:

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

Soluzione generica n. 2

L’approccio sopra riportato ha uno svantaggio: come in Come convertire un doppio in un array di byte in swift? , in realtà funziona solo con tipi “semplici” come numeri interi e tipi a virgola mobile. I tipi “complessi” come Array e String hanno puntatori (nascosti) alla memoria sottostante e non possono essere passati in giro semplicemente copiando la struttura stessa. Inoltre, non funzionerebbe con i tipi di riferimento che sono solo indicatori della reale memorizzazione degli oggetti.

Quindi risolvere il problema, si può

  • Definire un protocollo che definisce i metodi per la conversione in Data e viceversa:

     protocol DataConvertible { init?(data: Data) var data: Data { get } } 
  • Implementa le conversioni come metodi predefiniti in un’estensione di protocollo:

     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)) } } 

    Ho scelto un inizializzatore disponibile qui che controlla che il numero di byte fornito corrisponda alla dimensione del tipo.

  • E infine dichiarare la conformità a tutti i tipi che possono essere tranquillamente convertiti in Data e viceversa:

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

Ciò rende la conversione ancora più elegante:

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

Il vantaggio del secondo approccio è che non è ansible inavvertitamente fare conversioni non sicure. Lo svantaggio è che devi elencare tutti i tipi “sicuri” in modo esplicito.

Si potrebbe anche implementare il protocollo per altri tipi che richiedono una conversione non banale, come ad esempio:

 extension String: 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)! } } 

oppure implementare i metodi di conversione nei propri tipi per fare tutto ciò che è necessario in modo da serializzare e deserializzare un valore.

È ansible ottenere un puntatore non sicuro sugli oggetti mutabili usando withUnsafePointer :

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

Non conosco un modo per ottenerne uno per oggetti immutabili, perché l’operatore inout funziona solo su oggetti mutabili.

Questo è dimostrato nella risposta a cui ti sei collegato.

Nel mio caso, la risposta di Martin R ha aiutato, ma il risultato è stato invertito. Così ho fatto un piccolo cambiamento nel suo codice:

 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)) } } 

Il problema è legato a LittleEndian e BigEndian.