gpt4 book ai didi

swift - 往返于数据的Swift数字类型

转载 作者:行者123 更新时间:2023-11-30 10:44:05 25 4
gpt4 key购买 nike

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

最佳答案

注意:该代码现已针对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值的切片获得的。

因此,将字节复制到以下值更安全:

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

上面的方法有一个缺点:它实际上仅适用于“琐碎的”
类型,例如整数和浮点类型。 “复杂”类型,例如 Array
String具有(隐藏)指向基础存储的指针,并且不能
通过仅复制结构本身来传递。它也无法使用
引用类型,它们只是指向实际对象存储的指针。

所以解决这个问题,可以


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


我在这里选择了一个失败的初始化程序,该初始化程序检查提供的字节数
匹配类型的大小。
最后声明对所有可以安全转换为 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)
}
}


或以您自己的类型实现转换方法以执行任何操作
因此必须对值进行序列化和反序列化。

字节顺序

上面的方法没有字节顺序转换,数据总是在
主机字节顺序。对于独立于平台的表示形式(例如
“ 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/56167897/

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