webentwicklung-frage-antwort-db.com.de

Schnelle Strukturen nach NSData und zurück

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

18
niklassaers

Ich bekomme nicht wirklich Feedback, das ist die Lösung, mit der ich am Ende fertig bin:

  1. Machen Sie encode() und decode() Funktionen für meine Struktur
  2. Ändern Sie Int in Int64, damit die Int auf 32-Bit- und 64-Bit-Plattformen dieselbe Größe hat
  3. Eine Zwischenstruktur (ArchivedPacket) haben, die weder String noch Data, sondern nur Int64 hat

Hier 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
    }
}
11
niklassaers

Swift 3

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)")

Anmerkungen

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.

Aktualisieren

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

5
Jeff

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.

3
Martin

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 .

1
Mazyod