随着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 回答
注意:代码现已更新为 Swift 5 (Xcode 10.2) . (可以在编辑历史记录中找到Swift 3和Swift 4.2版本 . )现在也可以正确处理未对齐的数据 .
如何从值创建数据
从Swift 4.2开始,可以使用简单的值创建数据
说明:
withUnsafeBytes(of: value)使用覆盖值的原始字节的缓冲区指针调用闭包 .
原始缓冲区指针是一个字节序列,因此Data($0)可用于创建数据 .
如何从Data中检索值
从Swift 5开始,
Data
的withUnsafeBytes(_:)用字节的“无类型”UnsafeMutableRawBufferPointer
调用闭包 . 通过将内存“绑定”到所需的值类型来获得“类型”指针,最后取消引用类型指针:这种方法存在一个问题:将内存绑定到
Double
(或任何其他类型)要求它是针对该类型的属性对齐(此处:对齐到8字节地址) . 但这并不能保证,例如如果数据是作为另一个Data
值的切片获得的 .因此,将字节复制到值更安全:
说明:
withUnsafeMutableBytes(of:_:)使用覆盖值的原始字节的可变缓冲区指针调用闭包 .
DataProtocol
(Data
符合)的copyBytes(to:)方法将数据从数据复制到该缓冲区 .copyBytes()
的返回值是复制的字节数 . 它等于目标缓冲区的大小,如果数据不包含足够的字节,则更少 .通用解决方案#1
现在可以轻松地将上述转换实现为
struct Data
的通用方法:这里添加了约束
T: ExpressibleByIntegerLiteral
,这样我们就可以很容易地将值初始化为“零” - 这实际上并不是一个限制,因为这种方法无论如何都可以与“trival”(整数和浮点)类型一起使用,见下文 .例:
同样,您可以将数组转换为
Data
并返回:例:
通用解决方案#2
上述方法有一个缺点:它实际上只适用于整数和浮点类型等"trivial"类型 .
Array
类型如Array
和String
具有指向底层存储的(隐藏)指针,并且不能仅通过复制结构本身来传递 . 它也不适用于只是指向真实对象存储的引用类型 .可以解决这个问题
Data
和返回的方法:我在这里选择了一个可用的初始化器,它检查提供的字节数是否与类型的大小相匹配 .
Data
并返回的类型:这使得转换更加优雅:
第二种方法的优点是您不会无意中进行不安全的转换 . 缺点是您必须明确列出所有“安全”类型 .
您还可以为需要进行非平凡转换的其他类型实现协议,例如:
或者在您自己的类型中实现转换方法以执行必要的操作,以便序列化和反序列化值 .
字节顺序
在上述方法中没有进行字节顺序转换,数据总是按主机字节顺序排列 . 对于平台无关表示(例如“大端”,也称为“网络”字节顺序),使用相应的整数属性resp . 初始化 . 例如:
当然,这种转换也可以在通用中进行转换方法 .
您可以使用withUnsafePointer获取指向 mutable 对象的不安全指针:
我不知道为不可变对象获取一个的方法,因为inout运算符只适用于可变对象 .
这与你所链接的答案有关 .
就我而言,_2618196的答案有所帮助,但结果却被颠倒了 . 所以我对他的代码进行了一些小改动:
这个问题与LittleEndian和BigEndian有关 .