Remarque: La réponse (écrit à l'origine pour Swift 3) a été mis à jour pour
Swift 4.2/Xcode 10 maintenant, ça fait quelques simplifications possibles.
Certaines de mes suggestions précédentes invoquée undefined
comportement, cela a été corrigé. J'ai également ajouté des remarques sur les octets de commande et d'alignement.
Comment créer Data
de la valeur
Comme de Swift 4.2, les données peuvent être créés à partir d'une valeur simplement avec
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
Explication:
-
withUnsafeBytes(of: value)
invoque la fermeture avec un pointeur de tampon couvrant les premières octets de la valeur.
- Un raw pointeur de tampon est une séquence d'octets, donc
Data($0)
peut être utilisé pour créer les données.
Avant de Swift 4.2, il était nécessaire de créer une mutable copie de la première:
let value = 42.13
var mutableValue = value
let data = withUnsafeBytes(of: &mutableValue) { Data.init($0) }
Comment récupérer une valeur à partir d' Data
NSData
eu bytes
propriété afin d'obtenir l'accès au stockage sous-jacent.
struct Data
a un générique withUnsafeBytes(_:)
plutôt la méthode qui peut être utilisée comme ceci:
let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in
return ptr.pointee
}
print(value) // 42.13
Si l' ContentType
peut être déduit du contexte, alors il n'a pas besoin d'être spécifié
dans la fermeture, donc cela peut être simplifié à l'
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 #1
Le ci-dessus conversions peuvent maintenant être facilement mises en œuvre que les méthodes génériques de struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.pointee }
}
}
Exemple:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
let roundtrip = data.to(type: Double.self)
print(roundtrip) // 42.13
De même, vous pouvez convertir des tableaux d' Data
et retour:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] {
return self.withUnsafeBytes {
[T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout<T>.stride))
}
}
}
Exemple:
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]
Solution générique #2
Cette approche a un inconvénient: Comme dans
Comment faire pour convertir un double en un tableau d'octets dans swift?,
il fonctionne uniquement avec la "simple"
des types comme les entiers et les décimaux. "Complexe" des types comme Array
et String
ont (caché) des pointeurs vers le stockage sous-jacent et ne peut pas être
passé autour par une simple copie de la structure elle-même. Il permet également de ne pas travailler avec
les types de référence qui sont juste des pointeurs à l'objet réel de stockage.
Afin de résoudre 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 }
}
-
Mettre en œuvre les conversions que les méthodes par défaut dans une extension du protocole:
extension DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<Self>.size else { return nil }
self = data.withUnsafeBytes { $0.pointee }
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
J'ai choisi un failable initialiseur ici qui vérifie que le nombre d'octets à condition
correspond à la taille du type.
-
Et enfin déclarer la conformité à tous les types qui peuvent en toute sécurité être converti Data
et retour:
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
Cela rend la conversion plus élégante:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
L'avantage de la deuxième méthode est que vous ne pouvez pas, par inadvertance, ne dangereux à des conversions.
L'inconvénient est que vous avez la liste de tous "safe" types explicitement.
Vous pourriez également mettre en œuvre le protocole pour les autres types qui nécessitent un non-trivial
la conversion, par exemple:
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)!
}
}
ou de mettre en œuvre les méthodes de conversion dans vos propres types de faire tout ce qui est
nécessaire pour sérialiser et désérialiser une valeur.
D'autres remarques
L'ordre des octets de
Pas d'ordre d'octet conversion est effectuée dans les méthodes ci-dessus, les données sont toujours en
l'ordre des octets de l'hôte. Pour une plate-forme indépendante de la représentation (p. ex.
"big endian" aka "réseau" d'ordre des octets), utilisez le raccourci entier
propriétés resp. les initialiseurs. Par exemple:
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
}
Bien sûr, cette conversion peut également être fait en général, dans le générique
méthode de conversion.
L'alignement
Les méthodes ci-dessus pour extraire une valeur d' Data
tous supposons que l'
les données sont correctement alignés pour le type de valeur. Si cela n'est pas garanti,
subdata(in:)
peut être utilisé pour créer une copie avec alignés de stockage sous-jacent.