Da sich Swift 3 Data
stattdessen zu [UInt8]
neigt, versuche ich herauszufinden, wie verschiedene Zahlentypen (UInt8, Double, Float, Int64 usw.) als Datenobjekte am effizientesten / idiomatischsten codiert / decodiert werden können.
Es gibt diese Antwort für die Verwendung von [UInt8] , aber es scheint verschiedene Zeiger-APIs zu verwenden, die ich in Data nicht finden kann.
Ich möchte im Grunde einige benutzerdefinierte Erweiterungen, die ungefähr so aussehen:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
Der Teil, der mir wirklich entgeht, ich habe eine Reihe von Dokumenten durchgesehen, ist, wie ich aus jeder Grundstruktur (die alle Zahlen sind) eine Art Zeigersache (OpaquePointer oder BufferPointer oder UnsafePointer?) Beziehen kann. In C würde ich einfach ein kaufmännisches Und davor schlagen, und los geht's.
quelle
Antworten:
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 schafftAb Swift 4.2 können Daten aus einem Wert einfach mit erstellt werden
Erläuterung:
withUnsafeBytes(of: value)
Ruft den Abschluss mit einem Pufferzeiger auf, der die Rohbytes des Werts abdeckt.Data($0)
kann daher zum Erstellen der Daten verwendet werden.So rufen Sie einen Wert ab
Data
Ab Swift 5 ruft das
withUnsafeBytes(_:)
ofData
den Abschluss mit einem "untyped"UnsafeMutableRawBufferPointer
für die Bytes auf. Dieload(fromByteOffset:as:)
Methode, die den Wert aus dem Speicher liest: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 :
Erläuterung:
withUnsafeMutableBytes(of:_:)
Ruft den Abschluss mit einem veränderlichen Pufferzeiger auf, der die Rohbytes des Werts abdeckt.copyBytes(to:)
Verfahren vonDataProtocol
(zu demData
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
: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:
Ebenso können Sie Arrays hin
Data
und zurück konvertieren :Beispiel:
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
undString
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:Implementieren Sie die Konvertierungen als Standardmethoden in einer Protokollerweiterung:
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:Dies macht den Umbau noch eleganter:
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.
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:
Natürlich kann diese Konvertierung auch allgemein in der generischen Konvertierungsmethode durchgeführt werden.
quelle
var
Kopie des Anfangswertes erstellen müssen, dass wir die Bytes zweimal kopieren? In meinem aktuellen Anwendungsfall wandle ich sie in Datenstrukturen um, damit ichappend
sie in einen wachsenden Strom von Bytes umwandeln kann . In Straight C ist dies so einfach wie*(cPointer + offset) = originalValue
. Die Bytes werden also nur einmal kopiert.ptr: UnsafeMutablePointer<UInt8>
, können Sie dem referenzierten Speicher etwas zuweisen,UnsafeMutablePointer<T>(ptr + offset).pointee = value
das genau Ihrem Swift-Code entspricht. Es gibt ein potenzielles Problem: Einige Prozessoren erlauben nur einen ausgerichteten Speicherzugriff, z. B. können Sie ein Int nicht an einem ungeraden Speicherort speichern. Ich weiß nicht, ob dies für die derzeit verwendeten Intel- und ARM-Prozessoren gilt.extension Array: DataConvertible where Element: DataConvertible
. Das ist in Swift 3 nicht möglich, aber für Swift 4 geplant (soweit ich weiß). Vergleichen Sie "Bedingte Konformitäten" in github.com/apple/swift/blob/master/docs/…Int.self
wieInt.Type
?Sie können einen unsicheren Zeiger auf veränderbare Objekte erhalten, indem Sie Folgendes verwenden
withUnsafePointer
:Ich kenne keine Möglichkeit, eine für unveränderliche Objekte zu erhalten, da der Operator inout nur für veränderbare Objekte funktioniert.
Dies wird in der Antwort gezeigt, auf die Sie verlinkt haben.
quelle
In meinem Fall hat die Antwort von Martin R geholfen, aber das Ergebnis wurde umgekehrt. Also habe ich eine kleine Änderung in seinem Code vorgenommen:
Das Problem hängt mit LittleEndian und BigEndian zusammen.
quelle