Swift 在范围内改变数据

标签 swift nsdata unsafe-pointers

我正在构建一个 Data看起来像这样的对象:

struct StructuredData {
  var crc: UInt16
  var someData: UInt32
  var someMoreData: UInt64
  // etc.
}

我正在运行一个从字节 2 开始并处理长度 12 的 CRC 算法。

返回CRC时,必须存在于Data的开头目的。在我看来,我的选择是:

  1. 生成 Data不包含 CRC 的对象,对其进行处理,然后构建另一个 Data对象(这样我现在拥有的 CRC 值将位于 Data 对象的开头。

  2. 生成数据对象以包含一个清零的 CRC 作为开始,然后在 [0..<2] 范围内对数据进行变异.

显然,2 会更可取,因为它使用更少的内存和更少的处理,但我不确定是否需要这种类型的优化。我还是宁愿选择 2,除非我不知道如何在给定的索引范围内改变数据。非常感谢任何帮助。

最佳答案

我不推荐使用这种改变 Data 的方式:

data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))

请试试这个:

data.replaceSubrange(0..<2, with: &self.crc, count: 2)

很难解释为什么,但我会尝试......

在 Swift 中,inout 参数以复制输入复制输出语义工作。当你写这样的东西时:

aMethod(&param)
  • Swift 分配一些足够大的区域来容纳param的内容,

  • param复制到区域中,(copy-in)

  • 通过传递区域地址调用方法,

  • 并且当从调用返回时,将该区域的内容复制回param(复制输出)。

在许多情况下,Swift 通过仅传递 param 的实际地址来优化(甚至可能在 -Onone 设置中发生)这些步骤,但没有明确记录.

因此,当 inout 参数被传递给 UnsafeBufferPointer 的初始化器时,UnsafeBufferPointer 接收到的地址可能指向一个时间区域, 会在初始化完成后立即释放。

因此,replaceSubrange(_:with:)可以将已释放区域中的字节复制到Data中。

我相信第一个代码在这种情况下会起作用,因为 crc 是一个结构的属性,但如果有一个简单而安全的替代方案,您最好避免不安全的方式。


对 Brandon Mantzey 自己的回答的评论的补充。

data.append(UnsafeBufferPointer(start: &self.crcOfRecordData, count: 1))

在上面的意思中使用safe。这不是安全,原因与上述相同。

我会这样写:

data.append(Data(bytes: &self.crcOfRecordData, count: MemoryLayout<UInt16>.size))

(假设crcOfRecordData的类型为UInt16。)

如果您不喜欢创建额外的 Data 实例,您可以将其写为:

withUnsafeBytes(of: &self.crcOfRecordData) {urbp in
     data.append(urbp.baseAddress!.assumingMemoryBound(to: UInt8.self), count: MemoryLayout<UInt16>.size)
}

注释中没有提到这一点,但在上面安全的含义中,下面这行不是安全

let uint32Data = Data(buffer: UnsafeBufferPointer(start: &self.someData, count: 1))

都是一样的道理。

我会这样写:

let uint32Data = Data(bytes: &self.someData, count: MemoryLayout<UInt32>.size)

尽管如此,可观察到的意外行为可能会在某些非常有限的条件下发生,而且概率很小。

这种行为只有在满足以下两个条件时才会发生:

  1. Swift 编译器生成非优化的拷贝入拷贝出代码

  2. 在非常狭窄的时间段之间,由于时间区域被释放直到append方法(或Data.init)完成复制整个内容,区域被修改用于其他用途。

在当前的 Swift 实现中,条件 #1 仅在有限的情况下变为真。

条件 #2 很少只在多线程环境中发生。 (不过,Apple 的框架使用了许多隐藏线程,您可以在 Xcode 的调试器中找到它。)

事实上,我还没有看到关于上述不安全案例的任何问题,我的安全可能有点矫枉过正

但是替代安全代码并不那么复杂,对吗? 在我看来,您最好习惯使用all-cases-safe代码。

关于Swift 在范围内改变数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52503006/

相关文章:

swift - 为什么我的结构在方法链中变得不可变?

ios - Swift 从 UnsafeMutablePointer<Int8> 获取数据

c# - 在指向整数的指针数组上动态设置值

Swift 如何从条件绑定(bind)必须具有可选类型的 do catch 返回元组?

swift - 在 Xcode 8.0 Swift 3.0 上检测手势

ios - Swift UILabel 文本链接

ios - 使用 "Pause"和 "Resume"功能从服务器下载数据

ios - 检查NSData是否包含特定值的条件语句

objective-c - 读取 9 位、10 位或 11 位(精确) block 中的数据并在 Objective-C/C 中转换为整数

swift - 将指针从 Swift 2 转换为 Swift 3