Note:代码已更新为Swift 5(Xcode 10.2)现在。 (Swift 3 和 Swift 4.2 版本可以在编辑历史记录中找到。)现在也可以正确处理可能未对齐的数据。
如何创建Data
从一个值
从 Swift 4.2 开始,可以简单地从值创建数据
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
解释:
-
withUnsafeBytes(of: value)使用覆盖值的原始字节的缓冲区指针调用闭包。
- 原始缓冲区指针是一个字节序列,因此Data($0)可用于创建数据。
如何从中检索值Data
从 Swift 5 开始,withUnsafeBytes(_:) of Data
使用“无类型”调用闭包UnsafeMutableRawBufferPointer
到字节。这load(fromByteOffset:as:)从内存中读取值的方法:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
这种方法有一个问题:它要求内存具有属性aligned类型(此处:与 8 字节地址对齐)。但这并不能保证,例如如果数据是作为另一个数据的切片获得的Data
value.
因此更安全的是copy值的字节:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
解释:
-
withUnsafeMutableBytes(of:_:)使用覆盖值的原始字节的可变缓冲区指针调用闭包。
- The copyBytes(to:)的方法
DataProtocol
(对于其中Data
符合)将字节从数据复制到该缓冲区。
返回值copyBytes()
是复制的字节数。它等于目标缓冲区的大小,或者如果数据不包含足够的字节则小于目标缓冲区的大小。
通用解决方案#1
现在可以轻松地将上述转换实现为通用方法struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}
约束条件T: ExpressibleByIntegerLiteral
在这里添加,以便我们可以轻松地将值初始化为“零”——这实际上并不是一个限制,因为无论如何该方法都可以与“trival”(整数和浮点)类型一起使用,请参见下文。
Example:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = data.to(type: Double.self) {
print(roundtrip) // 42.13
} else {
print("not enough data")
}
同样,您可以转换arrays to Data
然后回来:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
}
Example:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]
通用解决方案#2
上述方法有一个缺点:它实际上只适用于“琐碎”的情况
整数和浮点类型等类型。 “复杂”类型如Array
and String
有(隐藏)指向底层存储的指针,并且不能
通过复制结构本身来传递。它也不适用于
引用类型只是指向真实对象存储的指针。
那么解决这个问题就可以
-
定义一个协议,定义转换为的方法Data
然后回来:
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
-
将转换实现为协议扩展中的默认方法:
extension DataConvertible where Self: ExpressibleByIntegerLiteral{
init?(data: Data) {
var value: Self = 0
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
我选择了一个failable这里的初始化程序检查提供的字节数
与类型的大小相匹配。
-
最后声明与所有可以安全转换的类型的一致性Data
然后回来:
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
这使得转换更加优雅:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
第二种方法的优点是您不会无意中进行不安全的转换。缺点是您必须显式列出所有“安全”类型。
您还可以为需要重要转换的其他类型实现协议,例如:
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
// Note: a conversion to UTF-8 cannot fail.
return Data(self.utf8)
}
}
或在您自己的类型中实现转换方法来执行任何操作
因此序列化和反序列化一个值是必要的。
字节顺序
上述方法中没有进行字节顺序转换,数据始终在
主机字节顺序。对于独立于平台的表示(例如
“big endian”又名“网络”字节顺序),使用相应的整数
属性分别。初始化器。例如:
let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip)) // 1000
}
当然这种转换也可以一般地完成,在通用的
转换方法。