Swift 3倾向于Data
而不是[UInt8]
,我试图找出编码/解码各种数字类型(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
真正逃避我的部分,我已经浏览了一堆文档,我是如何从任何基本结构(所有数字都是这样的)获得某种指针的东西(OpaquePointer或BufferPointer或UnsafePointer?) )。在C中,我只是在它前面拍一个&符号,然后就可以了。
答案 0 :(得分:198)
注意:现在已经为 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开始,Data
的{{3}}用字节的“无类型”UnsafeMutableRawBufferPointer
调用闭包。 withUnsafeBytes(_:)
方法从内存中读取值:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.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: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
说明:
load(fromByteOffset:as:)
使用覆盖值的原始字节的可变缓冲区指针调用闭包。DataProtocol
Data
方法(copyBytes()
符合)将数据从数据复制到该缓冲区。 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
此处添加约束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")
}
,以便我们可以轻松地将值初始化为“零” - 这实际上不是限制因为此方法可以与“trival”(整数和浮点)类型一起使用,见下文。
示例:
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
}
}
并返回:
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]
示例:
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: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
在协议扩展中将转换实现为默认方法:
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)
}
}
第二种方法的优点是您不会无意中进行不安全的转换。缺点是您必须明确列出所有“安全”类型。
您还可以为需要进行非平凡转换的其他类型实现协议,例如:
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
}
或在您自己的类型中实现转换方法以执行任何操作 必要时序列化和反序列化一个值。
在上述方法中没有进行字节顺序转换,数据总是在 主机字节顺序。对于独立于平台的表示(例如, “big endian”又名“network”字节顺序),使用相应的整数 属性resp。初始化。例如:
_id
当然,这种转换也可以在通用中进行 转换方法。
答案 1 :(得分:3)
您可以使用withUnsafePointer
获取指向可变对象的不安全指针:
withUnsafePointer(&input) { /* $0 is your pointer */ }
我不知道如何为不可变对象获取一个,因为inout运算符只适用于可变对象。
您在与之相关的答案中对此进行了演示。
答案 2 :(得分:2)
就我而言,Martin R的答案有所帮助但结果却被颠倒了。所以我对他的代码进行了一些小改动:
extension UInt16 : DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<UInt16>.size else {
return nil
}
self = data.withUnsafeBytes { $0.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))
}
}
问题与LittleEndian和BigEndian有关。