web-dev-qa-db-fra.com

Swift structs à NSData et retour

J'ai une structure contenant une structure et une NSObject que je veux sérialiser dans un objet NSData:

struct Packet {
  var name: String
  var index: Int
  var numberOfPackets: Int
  var data: NSData
}

var thePacket = Packet(name: name, index: i, numberOfPackets: numberOfPackets, data: packetData)

Comment puis-je sérialiser le paquet dans une NSData et comment le désérialiser au mieux?

En utilisant

var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))

de ne me donne que les pointeurs de nom et de données. J'explorais NSKeyedArchiver, mais je devais alors faire de Packet un objet et je préférerais le garder comme structure.

À votre santé

Nik

18
niklassaers

Ne recevant pas vraiment de retours, voici la solution que j'ai trouvée:

  1. Faire des fonctions encode() et decode() pour ma struct
  2. Remplacez Int par Int64 afin que Int ait la même taille sur les plates-formes 32 bits et 64 bits.
  3. Avoir une structure intermédiaire (ArchivedPacket) qui n'a pas de chaîne ou Data, mais seulement Int64

Voici mon code, je vous serais très reconnaissant de vos commentaires, surtout s’il existe des moyens moins encombrants de le faire:

public struct Packet {
    var name: String
    var index: Int64
    var numberOfPackets: Int64
    var data: NSData

    struct ArchivedPacket {
        var index : Int64
        var numberOfPackets : Int64
        var nameLength : Int64
        var dataLength : Int64
    }

    func archive() -> NSData {

        var archivedPacket = ArchivedPacket(index: Int64(self.index), numberOfPackets: Int64(self.numberOfPackets), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)), dataLength: Int64(self.data.length))

        var metadata = NSData(
            bytes: &archivedPacket,
            length: sizeof(ArchivedPacket)
        )

        let archivedData = NSMutableData(data: metadata)
        archivedData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
        archivedData.appendData(data)

        return archivedData
    }

    func unarchive(data: NSData!) -> Packet {
        var archivedPacket = ArchivedPacket(index: 0, numberOfPackets: 0, nameLength: 0, dataLength: 0)
        let archivedStructLength = sizeof(ArchivedPacket)

        let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
        archivedData.getBytes(&archivedPacket)

        let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
        let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))

        let nameData = data.subdataWithRange(nameRange)
        let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
        let theData = data.subdataWithRange(dataRange)

        let packet = Packet(name: name, index: archivedPacket.index, numberOfPackets: archivedPacket.numberOfPackets, data: theData)

        return packet
    }
}
11
niklassaers

Swift 3

Il s'agit d'un copier-coller inaltéré d'un Playground dans Xcode 8.2.1 qui fonctionne. C'est un peu plus simple que d'autres réponses.

import Foundation

enum WhizzoKind {
    case floom
    case bzzz
}

struct Whizzo {
    let name: String
    let num: Int
    let kind:WhizzoKind

    static func archive(w:Whizzo) -> Data {
        var fw = w
        return Data(bytes: &fw, count: MemoryLayout<Whizzo>.stride)
    }

    static func unarchive(d:Data) -> Whizzo {
        guard d.count == MemoryLayout<Whizzo>.stride else {
            fatalError("BOOM!")
        }

        var w:Whizzo?
        d.withUnsafeBytes({(bytes: UnsafePointer<Whizzo>)->Void in
            w = UnsafePointer<Whizzo>(bytes).pointee
        })
        return w!
    }
}

let thing = Whizzo(name:"Bob", num:77, kind:.bzzz)
print("thing = \(thing)")
let dataThing = Whizzo.archive(w: thing)
let convertedThing = Whizzo.unarchive(d: dataThing)
print("convertedThing = \(convertedThing)")

Remarques

Je ne pouvais pas créer les méthodes d'instance archive et unarchive car Data.init(bytes:​count:​) est en mutation sur le paramètre bytes? Et self n'est pas modifiable, donc ... Cela n'a aucun sens pour moi.

L'énumération WhizzoKind est là parce que c'est quelque chose qui m'importe. Ce n'est pas important pour l'exemple. Quelqu'un pourrait être paranoïaque à propos d'énums comme moi.

J'ai dû bricoler cette réponse à partir de 4 autres SO questions/réponses:

Et ces documents: - http://swiftdoc.org/v3.1/type/UnsafePointer/

Et en méditant sur la syntaxe de fermeture rapide jusqu'à ce que je veuille crier.

Donc, merci à ces SO autres demandeurs/auteurs.

Mettre à jour

Donc, cela va pas fonctionner sur plusieurs appareils. Par exemple, envoi d'iPhone 7 à Apple Watch. Parce que la stride est différente. L'exemple ci-dessus représente 80 octets sur iPhone 7 Simulator mais 40 octets sur Apple Watch Series 2 Simulator.

Il semble que l'approche (mais pas la syntaxe) de @niklassaers soit toujours la seule qui fonctionne. Je vais laisser cette réponse ici car cela pourrait aider les autres avec toutes les nouvelles modifications de la syntaxe et de l'API de Swift 3 entourant ce sujet.

Notre seul réel espoir est cette proposition Swift: https://github.com/Apple/Swift-evolution/blob/master/proposals/0166-Swift-archival-serialization.md

5
Jeff

J'ai utilisé l'exemple de Jeff pour créer la structure suivante:

struct Series {

var name: String?
var season: String?
var episode: String?

init(name: String?, season: String?, episode: String?) {
    self.name = name
    self.season = season
    self.episode = episode
}

static func archive(w: Series) -> Data {
    var fw = w
    return Data(bytes: &fw, count: MemoryLayout<Series>.stride)
}

static func unarchive(d: Data) -> Series {
    guard d.count == MemoryLayout<Series>.stride else {
        fatalError("Error!")
    }

    var w: Series?
    d.withUnsafeBytes({(bytes: UnsafePointer<Series>) -> Void in
        w = UnsafePointer<Series>(bytes).pointee
    })
    return w!
}

}

Comme Dag l'a mentionné, le tout est un peu fragile. Parfois, l'application se bloque lorsque le nom contient un espace ou un soulignement/soulignement, et parfois, elle se bloque sans raison. Dans tous les cas, le nom qui est désarchivé ressemble à ce '4\200a\256'. Étonnamment, cela n’est pas un problème en cas de saison ou d’épisode (comme dans "Saison 2"). Ici, les espaces ne forcent pas l'application à se bloquer.

Peut-être que c'est une alternative pour encoder les chaînes en utf8 mais je ne suis pas assez familier avec les méthodes archive/unarchive pour les adopter dans ce cas.

3
Martin

Il semble que cela soit sorti récemment, et pour moi cela semble solide. Je n'ai pas encore essayé ...

_ { https://github.com/a2/MessagePack.Swift } _


Eh bien, Swift n’a pas de méthode de sérialisation magique, si c’est ce que vous recherchez. Depuis les bons jours de C, lorsque vous avez une structure avec un pointeur, il s'agit d'un indicateur indiquant que vous ne pouvez pas sérialiser les octets de l'instance de cette structure sans suivre les pointeurs et récupérer leurs données. Même chose pour Swift.

En fonction de vos besoins et de vos contraintes en matière de sérialisation, je dirais que l'utilisation de NSCoding ou même de chaînes JSON nettoiera votre code et le rendra plus prévisible que l'état actuel. Bien sûr, vous aurez besoin d'écrire un mappeur, et il y a un temps système. Tout le monde vous dira ceci: "Mesurer en premier".

Maintenant, voici la partie intéressante:

Si vous vraiment voulez insérer vos données dans cette structure et diffuser le contenu sans construire le paquet autour de NSData comme vous le faites, vous pouvez réserver des octets à l'aide de Swift Tuples, qui fonctionne de la même manière que vous le feriez avec C en utilisant char[CONST]:

struct what { 
    var x = 3 
}    

sizeof(what)

$R0: Int = 8

struct the { 
    var y = (3, 4, 5, 7, 8, 9, 33) 
}    

sizeof(the)

$R1: Int = 56

Pour en dire un peu plus à ce sujet, je pense que c'est assez horrible, mais possible. Vous pouvez écrire dans l'emplacement de mémoire du tuple et y lire en utilisant quelque chose comme ceci .

1
Mazyod