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
Ne recevant pas vraiment de retours, voici la solution que j'ai trouvée:
encode()
et decode()
pour ma structInt
par Int64
afin que Int
ait la même taille sur les plates-formes 32 bits et 64 bits.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
}
}
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)")
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.
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
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.
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 .