Swift 结构到 NSData 并返回
Swift structs to NSData and back
我有一个包含结构和 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))
of只给我name和data的指针。我正在探索 NSKeyedArchiver
,但后来我不得不将 Packet 设为对象,我更愿意将其保留为结构。
干杯
尼克
没有真正得到任何反馈,这是我最终得到的解决方案:
- 为我的结构创建
encode()
和 decode()
函数
- 将
Int
更改为 Int64
以便 Int
在 32 位和 64 位平台上具有相同的大小
- 有一个中间结构 (ArchivedPacket),它没有字符串或
Data
,但只有 Int64
这是我的代码,非常感谢您的反馈,尤其是如果有更简单的方法来执行此操作:
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
}
}
这似乎是最近才出现的,对我来说它看起来很可靠。还没试过...
https://github.com/a2/MessagePack.swift
好吧,Swift 没有任何神奇的序列化方法,如果那是您所追求的。自从 C 的好日子以来,当你有一个带指针的结构时,这是一个标志,你不能序列化该结构实例的字节而不遵循指针并获取它们的数据。同样适用于 Swift.
根据您的序列化需求和限制,我想说使用 NSCoding
甚至 JSON 字符串将整理您的代码并使其比当前状态更可预测。当然,您需要编写一个映射器,这会产生开销。每个人都会告诉你:"Measure first".
现在,这是有趣的部分:
如果您真的想要在该结构中内联您的数据,并流式传输内容而不像您正在做的那样围绕 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
为了进一步说明这一点,我认为这很可怕,但有可能。您可以写入元组的内存位置并从中读取 using something like this.
Swift 5
如果您使用的是 Apple 平台,请立即使用 Codable
。参见 documentation。
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)")
备注
我无法创建 archive
和 unarchive
实例方法,因为 Data.init(bytes:count:)
正在改变 bytes
参数? self
不是可变的,所以...这对我来说毫无意义。
WhizzoKind
枚举在那里,因为这是我关心的事情。这对于示例并不重要。有人可能像我一样对枚举很偏执。
我不得不从其他 4 个 SO question/answers:
中拼凑出这个答案
- Extract struct from NSData in Swift
以及这些文档:
- http://swiftdoc.org/v3.1/type/UnsafePointer/
并沉思 Swift 闭包语法直到我想尖叫。
非常感谢其他 SO askers/authors。
更新
所以这将不能跨设备工作。例如,从 iPhone 7 发送到 Apple Watch。因为 stride
不同。上面的示例在 iPhone 7 模拟器上是 80 字节,但在 Apple Watch Series 2 模拟器上是 40 字节。
看起来 @niklassaers 的方法(但不是语法)仍然是唯一可行的方法。我将在此处留下这个答案,因为它可能会帮助其他人了解所有新的 Swift 3 语法和围绕该主题的 API 更改。
我们唯一真正的希望是这个 Swift 提议:https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md
我使用 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 提到的那样,整个事情有点脆弱。有时应用程序在名称包含空格或 underline/underscore 时崩溃,有时它会无缘无故地崩溃。在所有情况下,未存档的名称看起来都类似于此“40a6”。令人惊讶的是,对于季节或剧集(如“第 2 季”)来说,这不是问题。这里的空格不会强制应用程序崩溃。
也许将字符串编码为 utf8 是一种替代方法,但我对 archive/unarchive 方法不够熟悉,因此无法适应这种情况。
基本结构对象的最简单方法是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
}
我有一个包含结构和 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))
of只给我name和data的指针。我正在探索 NSKeyedArchiver
,但后来我不得不将 Packet 设为对象,我更愿意将其保留为结构。
干杯
尼克
没有真正得到任何反馈,这是我最终得到的解决方案:
- 为我的结构创建
encode()
和decode()
函数 - 将
Int
更改为Int64
以便Int
在 32 位和 64 位平台上具有相同的大小 - 有一个中间结构 (ArchivedPacket),它没有字符串或
Data
,但只有Int64
这是我的代码,非常感谢您的反馈,尤其是如果有更简单的方法来执行此操作:
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
}
}
这似乎是最近才出现的,对我来说它看起来很可靠。还没试过...
https://github.com/a2/MessagePack.swift
好吧,Swift 没有任何神奇的序列化方法,如果那是您所追求的。自从 C 的好日子以来,当你有一个带指针的结构时,这是一个标志,你不能序列化该结构实例的字节而不遵循指针并获取它们的数据。同样适用于 Swift.
根据您的序列化需求和限制,我想说使用 NSCoding
甚至 JSON 字符串将整理您的代码并使其比当前状态更可预测。当然,您需要编写一个映射器,这会产生开销。每个人都会告诉你:"Measure first".
现在,这是有趣的部分:
如果您真的想要在该结构中内联您的数据,并流式传输内容而不像您正在做的那样围绕 NSData
构建数据包,您可以保留使用 Swift Tuples
的字节,其工作方式与使用 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
为了进一步说明这一点,我认为这很可怕,但有可能。您可以写入元组的内存位置并从中读取 using something like this.
Swift 5
如果您使用的是 Apple 平台,请立即使用 Codable
。参见 documentation。
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)")
备注
我无法创建 archive
和 unarchive
实例方法,因为 Data.init(bytes:count:)
正在改变 bytes
参数? self
不是可变的,所以...这对我来说毫无意义。
WhizzoKind
枚举在那里,因为这是我关心的事情。这对于示例并不重要。有人可能像我一样对枚举很偏执。
我不得不从其他 4 个 SO question/answers:
中拼凑出这个答案- Extract struct from NSData in Swift
以及这些文档: - http://swiftdoc.org/v3.1/type/UnsafePointer/
并沉思 Swift 闭包语法直到我想尖叫。
非常感谢其他 SO askers/authors。
更新
所以这将不能跨设备工作。例如,从 iPhone 7 发送到 Apple Watch。因为 stride
不同。上面的示例在 iPhone 7 模拟器上是 80 字节,但在 Apple Watch Series 2 模拟器上是 40 字节。
看起来 @niklassaers 的方法(但不是语法)仍然是唯一可行的方法。我将在此处留下这个答案,因为它可能会帮助其他人了解所有新的 Swift 3 语法和围绕该主题的 API 更改。
我们唯一真正的希望是这个 Swift 提议:https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md
我使用 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 提到的那样,整个事情有点脆弱。有时应用程序在名称包含空格或 underline/underscore 时崩溃,有时它会无缘无故地崩溃。在所有情况下,未存档的名称看起来都类似于此“40a6”。令人惊讶的是,对于季节或剧集(如“第 2 季”)来说,这不是问题。这里的空格不会强制应用程序崩溃。
也许将字符串编码为 utf8 是一种替代方法,但我对 archive/unarchive 方法不够熟悉,因此无法适应这种情况。
基本结构对象的最简单方法是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
}