从数据往返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有关。