114 votes

aller-retour Swift nombre de types de/à partir de Données

Avec Swift 3 penchant vers l' Data au lieu de [UInt8], j'essaie de repérer quel est le plus efficace/idiomatiques façon de coder/décoder les martinets, les différents types de numéro (UInt8, Double, Float, Int64, etc) comme des objets de Données.

Il y a cette réponse de à l'aide de [UInt8], mais il semble être à l'aide de diverses pointeur Api que je ne trouve pas sur les Données.

J'aimerais fondamentalement certaines extensions personnalisées qui ressemble à quelque chose comme:

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

La partie qui vraiment m'échappe, j'ai regardé à travers un tas de docs, est de savoir comment je peux obtenir une sorte de pointeur de la chose (OpaquePointer ou BufferPointer ou UnsafePointer?) de toute base struct (tous les numéros). En C, je voudrais juste mettre une esperluette en face d'elle, et il ya go.

294voto

Martin R Points 105727

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.

3voto

zneak Points 45458

Vous pouvez obtenir un dangereux pointeur vers mutable objets à l'aide de withUnsafePointer:

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

Je ne sais pas de manière à en obtenir un des objets immuables, parce que le inout opérateur ne fonctionne que sur les objets mutables.

Ceci est démontré dans la réponse que vous y avez accédé.

2voto

Beto Caldas Points 803

Dans mon cas, Martin R's réponse 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<UInt16>.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é avec little-endian et gros-boutiste.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X