从数据往返Swift数字types
随着Swift 3倾向于Data
而不是[UInt8]
,我试图找出最有效/惯用的编码/解码方式作为数据对象的各种数字types(UInt8,Double,Float,Int64等)。
有使用[UInt8]这个答案 ,但它似乎是使用各种指针API,我无法find数据。
我想基本上一些自定义扩展,看起来像这样:
let input = 42.13 // implicit Double let bytes = input.data let roundtrip = bytes.to(Double) // --> 42.13
真正逃避我的部分,我查看了一堆文档,是如何从任何基本结构(所有数字都可以)获得某种指针的东西(OpaquePointer或BufferPointer或UnsafePointer?)。 在C中,我只是在它前面打一个&符号,然后你就去了。
如何从一个值创buildData
struct Data
有一个初始化程序
public init(bytes: UnsafeRawPointer, count: Int)
这可以以类似的方式在各种问题的答案中使用如何在Swift中将double转换为字节数组? 你链接到:
let input = 42.13 var value = input let data = withUnsafePointer(to: &value) { Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: input)) } print(data as NSData) // <713d0ad7 a3104540>
正如@zneak已经说过的,你可以只接受一个variables的地址,因此一个variables副本是由var value = value
。 在早期的Swift版本中,你可以通过将函数参数本身变成一个variables来实现,这已经不再被支持。
但是,使用初始化程序更容易
public init<SourceType>(buffer: UnsafeBufferPointer<SourceType>)
代替:
let input = 42.13 var value = input let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) print(data as NSData) // <713d0ad7 a3104540>
请注意,通用占位符SourceType
是从上下文自动推断的。
如何从Data
检索值
NSData
有一个bytes
属性来访问底层存储。 struct Data
有一个通用的
public func withUnsafeBytes<ResultType, ContentType>(_ body: @noescape (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType
相反,可以这样使用:
let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in return ptr.pointee } print(value) // 42.13
如果ContentType
可以从上下文中推断出来,那么它不需要在闭包中指定,所以这可以简化为
let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value: Double = data.withUnsafeBytes { $0.pointee } print(value) // 42.13
通用解决scheme#1
上面的转换现在可以很容易地实现为struct Data
通用方法:
extension Data { init<T>(from value: T) { var value = value self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) } func to<T>(type: T.Type) -> T { return self.withUnsafeBytes { $0.pointee } } }
例:
let input = 42.13 // implicit Double let data = Data(from: input) print(data as NSData) // <713d0ad7 a3104540> let roundtrip = data.to(type: Double.self) print(roundtrip) // 42.13
同样,您可以将数组转换为Data
并返回:
extension Data { init<T>(fromArray values: [T]) { var values = values self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count)) } func toArray<T>(type: T.Type) -> [T] { return self.withUnsafeBytes { [T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout<T>.stride)) } } }
例:
let input: [Int16] = [1, Int16.max, Int16.min] let data = Data(fromArray: input) print(data as NSData) // <0100ff7f 0080> let roundtrip = data.toArray(type: Int16.self) print(roundtrip) // [1, 32767, -32768]
通用解决scheme#2
上面的方法有一个缺点: 如何在Swift中将double转换为字节数组? ,它实际上只适用于“简单”types,如整数和浮点types。 像“ Array
和“ String
”这样的“复杂”types具有(隐藏的)指向底层存储的指针,不能通过复制结构本身来传递。 它也不适用于只是指向真实对象存储的引用types。
所以解决这个问题,可以
-
定义一个协议,它定义了转换为
Data
的方法并返回:protocol DataConvertible { init?(data: Data) var data: Data { get } }
-
在协议扩展中以默认方式实现转换:
extension DataConvertible { init?(data: Data) { guard data.count == MemoryLayout<Self>.size else { return nil } self = data.withUnsafeBytes { $0.pointee } } var data: Data { var value = self return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } }
我在这里select了一个failable初始化器,它检查提供的字节数是否与该types的大小相匹配。
-
最后声明符合所有可安全转换为
Data
并返回:extension Int : DataConvertible { } extension Float : DataConvertible { } extension Double : DataConvertible { } // add more types here ...
这使转换更加优雅:
let input = 42.13 let data = input.data print(data as NSData) // <713d0ad7 a3104540> if let roundtrip = Double(data: data) { print(roundtrip) // 42.13 }
第二种方法的优点是不能无意中进行不安全的转换。 缺点是你必须明确列出所有“安全”types。
您也可以为需要非平凡转换的其他types实现协议,例如:
extension String: DataConvertible { init?(data: Data) { self.init(data: data, encoding: .utf8) } var data: Data { // Note: a conversion to UTF-8 cannot fail. return self.data(using: .utf8)! } }
或者在你自己的types中实现转换方法来做任何必要的事情,以便序列化和反序列化一个值。
通过使用withUnsafePointer
你可以得到一个不安全的指向可变对象的指针:
withUnsafePointer(&input) { /* $0 is your pointer */ }
我不知道如何获得一个不可变对象的方法,因为inout运算符只能用于可变对象。
这已经在你已经链接到的答案中得到certificate。
就我而言, 马丁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有关。