往返 Swift 数字类型 to/from 数据
round trip Swift number types to/from Data
Swift 3 倾向于 Data
而不是 [UInt8]
,我想找出最 efficient/idiomatic 的 encode/decode 方法各种数字类型(UInt8、Double、Float、Int64 等)作为数据对象。
有this answer for using [UInt8],但它似乎使用了我在 Data 上找不到的各种指针 API。
我基本上想要一些看起来像这样的自定义扩展:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
真正让我困惑的部分是,我浏览了一堆文档,是如何从任何基本结构(所有的编号是)。在 C 中,我会在它前面打一个符号,然后就可以了。
您可以使用 withUnsafePointer
:
获取指向 mutable 对象的不安全指针
withUnsafePointer(&input) { /* [=10=] is your pointer */ }
我不知道有什么方法可以为不可变对象获取一个,因为 inout 运算符只适用于可变对象。
这在您链接到的答案中得到了证明。
注意:代码已更新为Swift 5 (Xcode 10.2)。 (Swift 3 和 Swift 4.2 版本可以在编辑历史中找到。)现在也可以正确处理未对齐的数据。
如何从值
创建Data
从 Swift 4.2 开始,只需使用
即可从值创建数据
let value = 42.13
let data = withUnsafeBytes(of: value) { Data([=10=]) }
print(data as NSData) // <713d0ad7 a3104540>
解释:
withUnsafeBytes(of: value)
使用覆盖值的原始字节的缓冲区指针调用闭包。
- 原始缓冲区指针是字节序列,因此
Data([=25=])
可用于创建数据。
如何从 Data
中检索值
从Swift 5开始,withUnsafeBytes(_:)
of Data
invokes the closure with an “untyped” UnsafeMutableRawBufferPointer
to the bytes. The load(fromByteOffset:as:)
方法从内存中读取值:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
[=11=].load(as: Double.self)
}
print(value) // 42.13
这种方法有一个问题:它要求内存属性 对齐类型(这里:对齐到8字节地址)。但这并不能保证,例如如果数据是作为另一个 Data
值的一部分获得的。
因此,复制字节到值更安全:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: [=12=])} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
解释:
withUnsafeMutableBytes(of:_:)
使用覆盖值原始字节的可变缓冲区指针调用闭包。
DataProtocol
的 copyBytes(to:)
方法(Data
符合)将字节从数据复制到该缓冲区。
copyBytes()
的return值是复制的字节数。它等于目标缓冲区的大小,或者如果数据不包含足够的字节则更小。
通用解决方案 #1
以上转换现在可以很容易地实现为 struct Data
:
的通用方法
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data([=13=]) }
}
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: [=13=])} )
return value
}
}
这里添加了约束T: ExpressibleByIntegerLiteral
,这样我们就可以轻松地将值初始化为“零”——这并不是真正的限制,因为这种方法可以与“trival”(整数和浮点数)一起使用不管怎样,请看下面。
示例:
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")
}
同样,您可以将 数组 转换为 Data
并返回:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data([=15=]) }
}
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: [=15=]) }
return array
}
}
示例:
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
上述方法有一个缺点:它实际上只适用于 "trivial"
类型,如整数和浮点类型。 "Complex" 类型 Array
和 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: [=18=])} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data([=18=]) }
}
}
我在这里选择了一个 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
}
第二种方法的优点是您不会无意中进行不安全的转换。缺点是您必须明确列出所有 "safe" 类型。
您还可以为需要非平凡转换的其他类型实现协议,例如:
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
}
当然这个转换也可以通用,在generic
转换方法。
就我而言,Martin R 的回答有所帮助,但结果却相反。所以我对他的代码做了一个小改动:
extension UInt16 : DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<UInt16>.size else {
return nil
}
self = data.withUnsafeBytes { [=10=].pointee }
}
var data: Data {
var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
}
该问题与 Little Endian 和 Big Endian 有关。
Swift 3 倾向于 Data
而不是 [UInt8]
,我想找出最 efficient/idiomatic 的 encode/decode 方法各种数字类型(UInt8、Double、Float、Int64 等)作为数据对象。
有this answer for using [UInt8],但它似乎使用了我在 Data 上找不到的各种指针 API。
我基本上想要一些看起来像这样的自定义扩展:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
真正让我困惑的部分是,我浏览了一堆文档,是如何从任何基本结构(所有的编号是)。在 C 中,我会在它前面打一个符号,然后就可以了。
您可以使用 withUnsafePointer
:
withUnsafePointer(&input) { /* [=10=] is your pointer */ }
我不知道有什么方法可以为不可变对象获取一个,因为 inout 运算符只适用于可变对象。
这在您链接到的答案中得到了证明。
注意:代码已更新为Swift 5 (Xcode 10.2)。 (Swift 3 和 Swift 4.2 版本可以在编辑历史中找到。)现在也可以正确处理未对齐的数据。
如何从值
创建Data
从 Swift 4.2 开始,只需使用
即可从值创建数据let value = 42.13
let data = withUnsafeBytes(of: value) { Data([=10=]) }
print(data as NSData) // <713d0ad7 a3104540>
解释:
withUnsafeBytes(of: value)
使用覆盖值的原始字节的缓冲区指针调用闭包。- 原始缓冲区指针是字节序列,因此
Data([=25=])
可用于创建数据。
如何从 Data
中检索值
从Swift 5开始,withUnsafeBytes(_:)
of Data
invokes the closure with an “untyped” UnsafeMutableRawBufferPointer
to the bytes. The load(fromByteOffset:as:)
方法从内存中读取值:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
[=11=].load(as: Double.self)
}
print(value) // 42.13
这种方法有一个问题:它要求内存属性 对齐类型(这里:对齐到8字节地址)。但这并不能保证,例如如果数据是作为另一个 Data
值的一部分获得的。
因此,复制字节到值更安全:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: [=12=])} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
解释:
withUnsafeMutableBytes(of:_:)
使用覆盖值原始字节的可变缓冲区指针调用闭包。DataProtocol
的copyBytes(to:)
方法(Data
符合)将字节从数据复制到该缓冲区。
copyBytes()
的return值是复制的字节数。它等于目标缓冲区的大小,或者如果数据不包含足够的字节则更小。
通用解决方案 #1
以上转换现在可以很容易地实现为 struct Data
:
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data([=13=]) }
}
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: [=13=])} )
return value
}
}
这里添加了约束T: ExpressibleByIntegerLiteral
,这样我们就可以轻松地将值初始化为“零”——这并不是真正的限制,因为这种方法可以与“trival”(整数和浮点数)一起使用不管怎样,请看下面。
示例:
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")
}
同样,您可以将 数组 转换为 Data
并返回:
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data([=15=]) }
}
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: [=15=]) }
return array
}
}
示例:
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
上述方法有一个缺点:它实际上只适用于 "trivial"
类型,如整数和浮点类型。 "Complex" 类型 Array
和 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: [=18=])} ) self = value } var data: Data { return withUnsafeBytes(of: self) { Data([=18=]) } } }
我在这里选择了一个 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
}
第二种方法的优点是您不会无意中进行不安全的转换。缺点是您必须明确列出所有 "safe" 类型。
您还可以为需要非平凡转换的其他类型实现协议,例如:
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
}
当然这个转换也可以通用,在generic 转换方法。
就我而言,Martin R 的回答有所帮助,但结果却相反。所以我对他的代码做了一个小改动:
extension UInt16 : DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<UInt16>.size else {
return nil
}
self = data.withUnsafeBytes { [=10=].pointee }
}
var data: Data {
var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
}
该问题与 Little Endian 和 Big Endian 有关。