Ich habe eine Struktur, die eine Struktur und eine NSObject
enthält, die ich in ein NSData
-Objekt serialisieren möchte:
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)
Wie kann ich das Paket am besten in eine NSData
serialisieren und wie kann ich es am besten deserialisieren?
Verwenden
var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))
von gibt mir nur die Hinweise auf Namen und Daten. Ich habe NSKeyedArchiver
erforscht, aber dann müsste ich Packet zu einem Objekt machen und würde es vorziehen, es als Struktur zu behalten.
Prost
Nik
Ich bekomme nicht wirklich Feedback, das ist die Lösung, mit der ich am Ende fertig bin:
encode()
und decode()
Funktionen für meine StrukturInt
in Int64
, damit die Int
auf 32-Bit- und 64-Bit-Plattformen dieselbe Größe hatData
, sondern nur Int64
hatHier ist mein Code, ich wäre sehr dankbar für Ihr Feedback, insbesondere wenn es weniger umständliche Möglichkeiten gibt, dies zu tun:
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
}
}
Dies ist ein unveränderter Copy-Paste aus einem Playground in Xcode 8.2.1, der funktioniert. Es ist ein bisschen einfacher als andere Antworten.
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)")
Ich konnte keine Instanzmethoden für archive
und unarchive
erstellen, da Data.init(bytes:count:)
den Parameter bytes
mutiert? Und self
ist nicht veränderbar, also ... Das ergab für mich keinen Sinn.
Das WhizzoKind
-Enum ist da, weil es mir etwas bedeutet. Für das Beispiel ist es nicht wichtig. Jemand könnte über Enomen paranoid sein wie ich.
Ich musste diese Antwort aus 4 anderen SO Fragen/Antworten zusammenstellen:
Und diese Dokumente: - http://swiftdoc.org/v3.1/type/UnsafePointer/
Und über die Swift-Verschlusssyntax meditieren, bis ich schreien wollte.
Danke auch an die anderen SO Fragesteller/Autoren.
Das wird nicht geräteübergreifend funktionieren. Zum Beispiel das Senden von iPhone 7 an die Apple Watch. Weil stride
anders ist. Das obige Beispiel umfasst 80 Byte für den iPhone 7 Simulator, 40 Byte für die Apple Watch Series 2 Simulator.
Es scheint, dass der Ansatz (aber keine Syntax) von @niklassaers immer noch der einzige ist, der funktionieren wird. Ich werde diese Antwort hier belassen, da sie anderen mit all den neuen Swift 3-Syntax- und API-Änderungen helfen könnte, die dieses Thema umgeben.
Unsere einzige wirkliche Hoffnung ist dieser Swift-Vorschlag: https://github.com/Apple/Swift-evolution/blob/master/proposals/0166-Swift-archival-serialization.md
Ich habe Jeffs Beispiel verwendet, um die folgende Struktur zu erstellen:
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!
}
}
Wie Dag erwähnt, ist das Ganze ein bisschen zerbrechlich. Manchmal stürzt die App ab, wenn der Name ein Leerzeichen oder einen Unterstrich/Unterstrich enthält, und manchmal stürzt sie einfach ohne Grund ab. In allen Fällen ähnelt der Name, der nicht archiviert ist, diesem '4\200a\256'. Überraschenderweise ist dies im Falle einer Staffel oder Episode (wie in "Staffel 2") kein Problem. Hier zwingt der Whitespace die App nicht zum Absturz.
Vielleicht ist es eine Alternative, um die Zeichenfolgen in utf8 zu kodieren, aber ich bin mit den Archiv/Unarchive-Methoden nicht genug vertraut, um sie für diesen Fall zu übernehmen.
Es scheint, als sei dies kürzlich herausgekommen, und für mich sieht es solide aus. Habe es noch nicht probiert ...
https://github.com/a2/MessagePack.Swift
Nun, Swift hat keine magische Serialisierungsmethode, wenn Sie das wollen. Seit den guten Tagen von C, wenn Sie über eine Struktur mit einem Zeiger verfügen, ist dies ein Flag, mit dem Sie die Bytes der Instanz dieser Struktur nicht serialisieren können, ohne den Zeigern zu folgen und ihre Daten abzurufen. Gleiches gilt für Swift.
Abhängig von Ihren Serialisierungsanforderungen und -beschränkungen würde ich sagen, dass die Verwendung von NSCoding
oder sogar JSON-Strings Ihren Code aufräumt und ihn vorhersagbarer macht als den aktuellen Status. Sicher, Sie müssen einen Mapper schreiben, und es gibt einen Overhead. Jeder wird es Ihnen sagen: "Messen Sie zuerst".
Nun, hier ist der interessante Teil:
Wenn Sie really Ihre Daten in diese Struktur einbetten möchten und den Inhalt streamen möchten, ohne dabei das Paket um NSData
herumzubauen, können Sie Bytes mit Swift Tuples
reservieren. Diese Funktion ähnelt der von Bytes C mit 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
Um dies etwas näher zu erläutern, finde ich es ziemlich schrecklich, aber möglich. Sie können in den Speicherort des Tuples schreiben und daraus lesen mit etwas wie .