Hinweis: Der Code wurde jetzt für Swift 5 (Xcode 10.2) aktualisiert . (Die Versionen Swift 3 und Swift 4.2 finden Sie im Bearbeitungsverlauf.) Auch möglicherweise nicht ausgerichtete Daten werden jetzt korrekt behandelt.
Wie man Data
aus einem Wert schafft
Ab Swift 4.2 können Daten aus einem Wert einfach mit erstellt werden
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
Erläuterung:
withUnsafeBytes(of: value)
Ruft den Abschluss mit einem Pufferzeiger auf, der die Rohbytes des Werts abdeckt.
- Ein Rohpufferzeiger ist eine Folge von Bytes und
Data($0)
kann daher zum Erstellen der Daten verwendet werden.
So rufen Sie einen Wert ab Data
Ab Swift 5 ruft das withUnsafeBytes(_:)
of Data
den Abschluss mit einem "untyped" UnsafeMutableRawBufferPointer
für die Bytes auf. Die load(fromByteOffset:as:)
Methode, die den Wert aus dem Speicher liest:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
Es gibt ein Problem mit diesem Ansatz: Es setzt voraus , dass die Speichereigenschaft ist ausgerichtet für den Typen (hier: zu einer 8-Byte - Adresse ausgerichtet). Dies ist jedoch nicht garantiert, z. B. wenn die Daten als Schnitt eines anderen Data
Werts erhalten wurden.
Es ist daher sicherer, die Bytes auf den Wert zu kopieren :
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
Erläuterung:
withUnsafeMutableBytes(of:_:)
Ruft den Abschluss mit einem veränderlichen Pufferzeiger auf, der die Rohbytes des Werts abdeckt.
- Das
copyBytes(to:)
Verfahren von DataProtocol
(zu dem Data
Konform) Kopiert Bytes aus den Daten in diesem Puffer.
Der Rückgabewert von copyBytes()
ist die Anzahl der kopierten Bytes. Sie entspricht der Größe des Zielpuffers oder weniger, wenn die Daten nicht genügend Bytes enthalten.
Generische Lösung # 1
Die oben genannten Konvertierungen können jetzt einfach als generische Methoden implementiert werden für struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}
Die Einschränkung T: ExpressibleByIntegerLiteral
wird hier hinzugefügt, damit wir den Wert leicht auf "Null" initialisieren können - das ist keine wirkliche Einschränkung, da diese Methode ohnehin mit "Trival" -Typen (Ganzzahl und Gleitkomma) verwendet werden kann, siehe unten.
Beispiel:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = data.to(type: Double.self) {
print(roundtrip) // 42.13
} else {
print("not enough data")
}
Ebenso können Sie Arrays hin Data
und zurück konvertieren :
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
}
Beispiel:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]
Generische Lösung # 2
Der obige Ansatz hat einen Nachteil: Er funktioniert tatsächlich nur mit "trivialen" Typen wie Ganzzahlen und Gleitkommatypen. "Komplexe" Typen mögen Array
und String
haben (versteckte) Zeiger auf den zugrunde liegenden Speicher und können nicht durch einfaches Kopieren der Struktur selbst weitergegeben werden. Es würde auch nicht mit Referenztypen funktionieren, die nur Zeiger auf den realen Objektspeicher sind.
Also kann man dieses Problem lösen, man kann
Definieren Sie ein Protokoll, das die Methoden für die Konvertierung nach Data
und zurück definiert:
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
Implementieren Sie die Konvertierungen als Standardmethoden in einer Protokollerweiterung:
extension DataConvertible where Self: ExpressibleByIntegerLiteral{
init?(data: Data) {
var value: Self = 0
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
Ich habe hier einen fehlgeschlagenen Initialisierer ausgewählt, der überprüft, ob die Anzahl der bereitgestellten Bytes mit der Größe des Typs übereinstimmt.
Und schließlich die Konformität mit allen Typen erklären, die sicher hin Data
und zurück konvertiert werden können:
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
Dies macht den Umbau noch eleganter:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
Der Vorteil des zweiten Ansatzes besteht darin, dass Sie nicht versehentlich unsichere Konvertierungen durchführen können. Der Nachteil ist, dass Sie alle "sicheren" Typen explizit auflisten müssen.
Sie können das Protokoll auch für andere Typen implementieren, für die eine nicht triviale Konvertierung erforderlich ist, z.
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
// Note: a conversion to UTF-8 cannot fail.
return Data(self.utf8)
}
}
oder implementieren Sie die Konvertierungsmethoden in Ihren eigenen Typen, um alles Notwendige zu tun, also serialisieren und deserialisieren Sie einen Wert.
Bytereihenfolge
Bei den obigen Methoden wird keine Konvertierung der Bytereihenfolge durchgeführt. Die Daten befinden sich immer in der Reihenfolge der Hostbytes. Verwenden Sie für eine plattformunabhängige Darstellung (z. B. "Big Endian", auch bekannt als "Netzwerk" -Byte-Reihenfolge) die entsprechenden Ganzzahl-Eigenschaften bzw. Initialisierer. Beispielsweise:
let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip)) // 1000
}
Natürlich kann diese Konvertierung auch allgemein in der generischen Konvertierungsmethode durchgeführt werden.