当前位置: 首页 > 面试题库 >

往返于数据的Swift数字类型

胥智
2023-03-14
问题内容

使用Swift 3Data而不是[UInt8],我试图找出最有效/惯用的编码/解码方式,将各种数字类型(UInt8,Double,Float,Int64等)转换为Data对象。

有这个答案使用[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和Swift4.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(_:)of会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添加了约束,以便我们可以轻松地将值初始化为“零”
–这并不是真正的限制,因为无论如何该方法都可以与“特制”(整数和浮点)类型一起使用,请参见下文。

例:

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)
    }
}

或以自己的类型实现转换方法以执行所需的任何操作,以便对值进行序列化和反序列化。

字节顺序

在上述方法中,没有字节顺序转换,数据始终以主机字节顺序进行。对于平台无关的表示形式(例如“大端”或“网络”字节顺序),请使用相应的整数属性。初始化程序。例如:

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
}

当然,这种转换通常也可以用通用转换方法来完成。



 类似资料:
  • 主要内容:1. 内置数据类型,2. 界限值,3. 类型别名,4. 类型安全,5.类型推断在使用任何编程语言进行编程时,都需要使用不同类型的变量来存储信息。 变量只是用于存储值的保留内存位置。 这意味着在创建变量时,会在内存中保留一些空间。 您可能希望存储各种数据类型的信息,如字符串,字符,宽字符,整数,浮点,布尔值等。根据变量的数据类型,操作系统分配内存并决定可以在保留内存中存储什么类型的内容。 1. 内置数据类型 Swift 4为程序员提供了丰富的内置和用户定义的数据类型。 在声明

  • 在我们使用任何程序语言编程时,需要使用各种数据类型来存储不同的信息。 变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。在声明变量时也可指定它的数据类型。 所有变量都具有数据类型,以决定能够存储哪种数据。 内置数据类型 Swift 提供了非常丰富的数据类型,以下列出了常用了几种数据类型: Int 一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int,长度与当

  • 问题内容: 如果我想对整数进行位操作,如何将其加载到?中?如何将其转换回int或long?我不太担心它的大小,它总是32或64位长。我只是想使用,,,和方法,而不是位运算符,但我无法找到一个简单的方法来初始化位集以数字类型。 问题答案: 以下代码从一个长值创建一个位集,反之亦然: 编辑:现在两个方向,@leftbrain:原因,你是对的

  • 问题内容: 我想制定一个程序来找出一个字符串中有多少个单词,并用空格,逗号或其他字符分隔。然后再将总数相加。 我正在做一个平均计算器,所以我想要数据的总数,然后将所有单词加起来。 问题答案: 更新: Xcode 10.2.x•Swift 5或更高版本 使用Foundation方法并将设置设置为选项: 或使用本机Swift 5的新属性和split方法: 扩展以支持子字符串:

  • 问题内容: 我有一个名为的表,其中包含三个字段: ID(自动递增int), UserId(int), 正在关注(int) 如果我有这样的数据: 我将如何找到用户2的朋友(即:用户2正在关注他们,而他们又跟随了用户2) 我想换句话说,如果用户“ a”跟随用户“ b”,而用户“ b”跟随用户“ a”,我该如何选择用户A? 问题答案: 试试这个:

  • 拿到了一份敏感词的json文件,想通过nodejs循环插入数据库中 但是每次都会报错,不知道是怎么回事,有大佬帮忙看看呗 报错 设计的表