web-dev-qa-db-ja.com

Swift NSDataへの構造体とその逆

私はNSObjectオブジェクトにシリアル化したいstructとNSDataを含むstructを持っています:

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)

パケットをNSDataにシリアライズするにはどうすればよいですか、またパケットをデシリアライズするにはどうすればよいですか?

使用する

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

の名前とデータのポインタのみを提供します。私はNSKeyedArchiverを探索していましたが、Packetをオブジェクトにする必要があり、それを構造体にしておくことを好みます。

乾杯

ニク

19
niklassaers

実際にはフィードバックはありませんが、これが私が最終的に解決したソリューションです:

  1. 構造体にencode()およびdecode()関数を作成する
  2. IntInt64に変更して、Intが32ビットプラットフォームと64ビットプラットフォームで同じサイズになるようにします。
  3. StringまたはDataはなく、Int64のみが含まれる中間構造体(ArchivedPacket)がある

これが私のコードです。特にこれを行うためのそれほど面倒な方法がない場合は、フィードバックに非常に感謝します。

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

スウィフト3

これは、動作するXcode 8.2.1のプレイグラウンドからの変更されていないコピーと貼り付けです。他の回答よりも少し簡単です。

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

ノート

archiveパラメータでData.init(bytes:​count:​)が変化しているため、unarchiveおよびbytesインスタンスメソッドを作成できませんでしたか? selfは変更できないので、...これは私には意味がありませんでした。

WhizzoKind列挙型がそこにあります。この例では重要ではありません。誰かが私のような列挙型について偏執狂かもしれない。

私は他の4つのSO質問/回答:

そしてこれらのドキュメント:- http://swiftdoc.org/v3.1/type/UnsafePointer/

そして、Swiftクロージャー構文について、私が叫びたくなるまで瞑想しました。

他のSO質問者/作者に感謝します。

更新

したがって、これはデバイス全体で機能します /。たとえば、iPhone 7からApple Watchに送信します。strideは異なるためです。上記の例は、iPhone 7シミュレータでは80バイトですが、Apple Watch Series 2 Simulator。

@niklassaersによる(構文ではなく)アプローチがまだ機能する唯一のものであるように見えます。この答えは、このトピックを取り巻くすべての新しいSwift 3構文およびAPIの変更で他の人を助ける可能性があるため、ここに残しておきます。

私たちの唯一の本当の希望はこれですSwiftプロポーザル: https://github.com/Apple/Swift-evolution/blob/master/proposals/0166-Swift -archival-serialization.md

5
Jeff

Jeffの例を使用して、次の構造体を作成しました。

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!
}

}

Dagが述べたように、全体は少し壊れやすいです。名前に空白または下線/アンダースコアが含まれていると、アプリがクラッシュする場合があります。また、理由もなくクラッシュする場合もあります。すべての場合において、アーカイブされていない名前は、この「4\200a\256」のようになります。驚いたことに、これはシーズンやエピソードの場合には問題になりません(「シーズン2」のように)。ここで、空白はアプリのクラッシュを強制しません。

文字列をutf8にエンコードする代わりになるかもしれませんが、私はこの場合にそれらを採用するためのアーカイブ/アンアーカイブメソッドに十分に精通していません。

3
Martin

これは最近出てきたようで、私にはそれはしっかりしているように見えます。まだ試していません...

https://github.com/a2/MessagePack.Swift


まあ、Swiftには、それがあなたの望みである場合、魔法のシリアル化メソッドはありません。Cの良い時代以来、ポインタを持つ構造体があるとき、それはあなたがフラグです。ポインタをたどり、データをフェッチせずに、その構造体のインスタンスのバイトをシリアル化することはできません。Swiftにも同じことが言えます。

シリアライゼーションのニーズと制約に応じて、NSCodingまたはJSON文字列を使用するとコードが整理され、現在の状態よりも予測しやすくなります。もちろん、マッパーを作成する必要があり、オーバーヘッドが発生します。誰もがこれを教えてくれます:「最初に測定」。

さて、ここに興味深い部分があります:

reallyがその構造体にデータをインライン化し、NSDataの周りにパケットを構築せずにコンテンツをストリーミングしたい場合、Swift Tuplesを使用してバイトを予約できます。これは、char[CONST]を使用してCでバイトを予約する方法とよく似ています。

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

これを少し拡張すると、かなり恐ろしいことですが、可能です。タプルのメモリの場所に書き込み、そこから読み取ることができます このようなものを使用

1
Mazyod