范围内的快速突变数据

时间:2018-09-25 16:38:58

标签: swift nsdata unsafe-pointers

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

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

我正在运行的CRC算法将从字节2开始,进程长度为12。

返回CRC时,它必须存在于Data对象的开头。如我所见,我的选择是:

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

  2. 生成数据对象以包含以零开始的CRC开头,然后对[0..<2]范围内的数据进行突变。

显然,2是更可取的,因为它使用更少的内存和更少的处理,但是我不确定这种类型的优化是否是必要的。我还是宁愿选择2,除了我不知道如何在给定的索引范围内对数据进行突变。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

我不建议使用以下方法来突变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复制到该区域(复制)

  • 调用该方法并传递区域地址,

  • ,当从呼叫返回时,将该区域的内容复制回param(复制)。

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

因此,当将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)
}

此内容未在评论中提及,但按照上述 safe 的含义,以下行不是 safe

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)完成了对整个内容的复制,所以对该区域进行了修改以供其他用途。

  3. p>

条件#1仅在当前Swift实施中的有限情况下成立。

条件2仅在多线程环境中很少发生。 (尽管,Apple的框架使用许多隐藏的线程,正如您在Xcode的调试器中可以找到的一样。)

实际上,对于上述不安全案件,我没有看到任何疑问,我的 safe 可能是 overkill

但是替代的 safe 代码并不那么复杂,对吗? 我认为,您最好习惯于使用 all-cases-safe 代码。

答案 1 :(得分:0)

我知道了。实际上,我遇到了一个语法错误,这使我感到困惑,因为我之前从未见过。

这是答案:

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