往返数据的往返Swift数字类型

时间:2016-06-25 00:31:50

标签: swift swift3 swift-data

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中,我只是在它前面拍一个&符号,然后就可以了。

3 个答案:

答案 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的返回值是复制的字节数。它等于目标缓冲区的大小,如果数据不包含足够的字节,则更少。

通用解决方案#1

以上转化现在可以轻松实现为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

通用解决方案#2

上述方法有一个缺点:它实际上只适用于“琐碎” 类型如整数和浮点类型。 “复杂”类型,如StringData有(隐藏)指向底层存储的指针,但不能 通过复制结构本身来传递。它也不适用 引用类型,它们只是指向真实对象存储的指针。

所以解决这个问题,可以

  • 定义一个协议,定义转换为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有关。