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

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?)从任何基本 struct (所有数字都是).在C中,我只需在它前面打一个符号,就可以了.

推荐答案

Note:现在代码已经更新了Swift 5(Xcode 10.2).(可以在编辑历史记录中找到Swift 3和Swift 4.2版本.)另外,现在可能正确处理了未对齐的数据.

How to create Data from a value

从Swift 4.2开始,只需使用

let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }

print(data as NSData) // <713d0ad7 a3104540>

说明:

How to retrieve a value from Data

从Swift 5开始,Data中的withUnsafeBytes(_:)调用闭包,并对字节进行"非类型化"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

这种方法有一个问题:它要求内存是类型的属性aligned(这里:与8字节地址对齐).但这并不能保证,例如,如果数据是作为另一个Data值的切片获得的.

因此,将字节数设置为copy会更安全:

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

说明:

返回值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")
}

类似地,您可以将arrays转换为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

上述方法有一个缺点:它实际上只适用于"琐碎的"

所以我们可以解决这个问题

  • 定义一个协议,该协议定义了转换为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) }
        }
    }
    

    我在这里 Select 了一个failable初始值设定项,它判断提供的字节数

  • 最后声明所有类型的一致性,这些类型可以安全地转换为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
}

当然,这种转换通常也可以在泛型中完成

Swift相关问答推荐

如何在Swift 中创建移动的uil标签?

如何消除SwiftUI中SF符号的填充

如何将CodingKeys作为数组而不是枚举传递到类外部的函数中?

在数组中查找最大y值的x值

使用变量(而非固定)的字符串分量进行Swift正则表达式

Firebase removeObserver 在 Swift 5 中不起作用

供应和普通数组中具有空值的 map 函数的行为不一致?

如何根据 SwiftUI 中的字体大小定义按钮的大小?

(SwiftLint)如果有正文,如何编写规则(可能是自定义),在{之后总是\n(换行)?

调用从 SwiftUI 视图传递到 PageViewController.Coordinator 的函数

用于 Swift 编码的 iOS 应用程序的 Ffmpeg

异步等待不会在项目中触发,但在 Xcode playground 中工作正常

如何获取 NSRunningApplication 的参数?

如何从 LLDB 调用带有断点的 Swift 函数?

swift中的SequenceType和CollectionType有什么区别?

如何使用 Swift/iOS 为人类书写的笔画制作动画?

Xcode 7.3 Swift 的语法高亮和代码完成问题

Swift - 导入我的 swift 类

在 unwind segue 之后执行 push segue

Alamofire:如何全局处理错误