gpt4 book ai didi

swift - 往返 Swift 数字类型到/从数据

转载 作者:IT王子 更新时间:2023-10-29 04:57:04 27 4
gpt4 key购买 nike

随着 Swift 3 倾向于 Data而不是 [UInt8] ,我试图找出将各种数字类型(UInt8、Double、Float、Int64 等)编码/解码为 Data 对象的最有效/惯用方法。

this answer for using [UInt8] ,但它似乎使用了我在数据上找不到的各种指针 API。

我基本上想要一些看起来像这样的自定义扩展:

let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13

真正让我望而却步的部分,我浏览了一堆文档,是我如何从任何基本结构(所有数字都是)中获得某种指针(OpaquePointer 或 BufferPointer 或 UnsafePointer?)。在 C 语言中,我会在它前面打一个&符号,然后就可以了。

最佳答案

注意:代码已更新为 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 开始, withUnsafeBytes(_:) Data使用“无类型” UnsafeMutableRawBufferPointer 调用闭包到字节。 load(fromByteOffset:as:) 方法从内存中读取值:
    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 的切片获得的值(value)。

    因此,将字节复制到值更安全:
    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

    解释:
  • withUnsafeMutableBytes(of:_:) 使用覆盖值的原始字节的可变缓冲区指针调用闭包。
  • copyBytes(to:) DataProtocol的方法(Data 符合)将字节从数据复制到该缓冲区。
  • copyBytes()的返回值是复制的字节数。它等于目标缓冲区的大小,如果数据不包含足够的字节,则小于等于目标缓冲区的大小。

    通用解决方案#1

    上述转换现在可以轻松实现为 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在这里添加,以便我们可以轻松地将值初始化为“零”——这并不是真正的限制,因为无论如何该方法都可以与“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($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]

    通用解决方案#2

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

    所以解决这个问题,一个人可以
  • 定义一个协议(protocol),该协议(protocol)定义了转换为 Data 的方法然后回来:
    protocol DataConvertible {
    init?(data: Data)
    var data: Data { get }
    }
  • 将转换实现为协议(protocol)扩展中的默认方法:
    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
    }

    第二种方法的优点是您不会无意中进行不安全的转换。缺点是您必须明确列出所有“安全”类型。

    您还可以为需要非平凡转换的其他类型实现协议(protocol),例如:
    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”又名“network”字节顺序),使用相应的整数
    属性初始化程序。例如:
    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
    }

    当然这种转换一般也可以做,在泛型中
    转换方法。

    关于swift - 往返 Swift 数字类型到/从数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38023838/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com