Swift结构化到NSData并返回

时间:2015-03-07 15:34:42

标签: swift serialization struct nsdata nskeyedarchiver

我有一个包含结构和NSObject的结构,我想将其序列化为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)

如何最好地将数据包序列化为NSData,以及如何最好地将其反序列化?

使用

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

只给出了名称和数据的指针。我正在探索NSKeyedArchiver,但后来我必须将Packet作为一个对象,我宁愿将它保留为结构。

干杯

的Nik

5 个答案:

答案 0 :(得分:11)

没有得到任何反馈,这是我最终得到的解决方案:

  1. 为我的结构
  2. 制作encode()decode()个函数
  3. Int更改为Int64,以便Int在32位和64位平台上具有相同的大小
  4. 有一个没有字符串或Data的中间结构(ArchivedPacket),但只有Int64
  5. 这是我的代码,我非常感谢您的反馈,特别是如果有这么简单的方法:

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

答案 1 :(得分:5)

Swift 3

这是来自Xcode 8.2.1中Playground的未经修改的复制粘贴。它比其他答案简单一点。

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

备注

我无法生成archiveunarchive实例方法,因为Data.init(bytes:​count:​)bytes参数上发生变异?并且self不可变,所以......这对我没有意义。

WhizzoKind枚举就在那里,因为这是我关心的事情。这个例子并不重要。对于像我这样的枚举,有人可能会偏执。

我不得不从另外4个问题/答案中拼凑出这个答案:

这些文档: - http://swiftdoc.org/v3.1/type/UnsafePointer/

冥想Swift闭包语法,直到我想尖叫。

非常感谢其他SO的作者/作者。

更新

所以这将 跨设备工作。例如,从iPhone 7发送到Apple Watch。因为stride不同。以上示例在iPhone 7 Simulator上为80字节,但在Apple Watch Series 2 Simulator上为40字节。

看起来@niklassaers的方法(但不是语法)仍然是唯一可行的方法。我将在这里留下这个答案,因为它可能会帮助其他人围绕这个主题进行所有新的Swift 3语法和API更改。

我们唯一真正的希望就是这个Swift提案:https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

答案 2 :(得分:3)

我使用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!
}

}

像达格一样提到整件事情有点脆弱。有时,当名称包含空格或下划线/下划线时,应用程序崩溃,有时它会毫无理由地崩溃。在所有情况下,未归档的名称看起来类似于此&#39; 4 \ 200a \ 256&#39;。令人惊讶的是,在季节或剧集的情况下这不是问题(例如在&#34;第2季和第34期;)。这里的空格不会强迫应用程序崩溃。

也许它是将字符串编码为utf8的替代方法,但我对于这种情况采用存档/解包方法并不熟悉。

答案 3 :(得分:1)

这似乎最近出现了,对我来说它看起来很稳固。还没试过......

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


嗯,Swift没有任何神奇的序列化方法,如果这就是你所追求的。从C的好日子开始,当你有一个带指针的结构时,这是一个标志,你不能​​在不遵循指针和获取数据的情况下序列化该结构实例的字节。同样适用于Swift。

根据您的序列化需求和约束,我会说使用NSCoding甚至JSON字符串将整理您的代码并使其比当前状态更具可预测性。当然,你需要编写一个映射器,并且有一个开销。每个人都会告诉你:“先测量”。

现在,这是有趣的部分:

如果您确实想要在该结构中内联您的数据,并在不按照NSData构建数据包的情况下流式传输内容,则可以使用Swift保留字节{{ 1}},它与使用Tuples

在C中保留字节的方式非常相似
char[CONST]

为了扩大这一点,我认为它非常可怕,但可能。您可以写入元组的内存位置并从中读取using something like this

答案 4 :(得分:1)

基本结构对象的最简单方法是PropertyListEncoder & PropertyListDecoder

这是示例代码;

Swift 5

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

func getDataFromPacket(packet: Packet) -> Data?{
  do{
    let data = try PropertyListEncoder.init().encode(packet)
    return data
  }catch let error as NSError{
    print(error.localizedDescription)
  }
    return nil
}

func getPacketFromData(data: Data) -> Packet?{
    do{
      let packet = try PropertyListDecoder.init().decode(Packet.self, from: data)
      return packet
    }catch let error as NSError{
      print(error.localizedDescription)
    }
    
    return nil
}