最近,我编写此代码时并没有考虑太多:
myObject.myCollection.forEach { myObject.removeItem($0) }
其中myObject.removeItem(_),将删除该项目myObject.myCollection。
myObject.removeItem(_)
myObject.myCollection
现在看一下代码,我对为什么它甚至起作用感到困惑-我是否不应该得到类似的例外Collection was mutated while being enumerated?当使用常规的for-in循环时,甚至可以使用相同的代码!
Collection was mutated while being enumerated
这是预期的行为吗?还是我很幸运,它没有崩溃?
这确实是预期的行为–这是由于ArraySwift(以及标准库中的许多其他集合)是一种具有写时复制语义的值类型的事实。这意味着其基础缓冲区(间接存储)将在发生突变时被 复制 (并且,为优化起见,仅当未唯一引用时)。
Array
当您使用Sequence(forEach(_:)或)标准for in循环迭代(例如数组)时,将根据序列的makeIterator()方法创建迭代器,并next()反复应用其方法以顺序生成元素。
Sequence
forEach(_:)
for in
makeIterator()
next()
您可以考虑遍历序列,如下所示:
let sequence = [1, 2, 3, 4] var iterator = sequence.makeIterator() // `next()` will return the next element, or `nil` if // it has reached the end sequence. while let element = iterator.next() { // do something with the element }
在使用的情况下Array,将an IndexingIterator用作其迭代器–通过简单地 将该集合 与迭代的当前索引一起 存储, 即可遍历给定集合的元素。每次next()调用时,基集合都使用索引下标,然后递增索引,直到达到为止endIndex(您可以在此处看到其确切的实现)。
IndexingIterator
endIndex
因此,当您要在循环中更改数组时,其底层缓冲区 不会被 唯一引用,因为迭代器也可以对其进行查看。这将强制复制缓冲区,myCollection然后使用该缓冲区。
myCollection
因此,现在有两个数组–一个正在迭代的数组,另一个是您正在变异的数组。只要myCollection的缓冲区保持唯一引用,循环中的任何其他突变都不会触发另一个副本。
因此,这意味着在枚举值时对具有值语义的集合进行变异是绝对安全的。枚举将遍历整个集合–完全独立于您所做的任何突变,因为它们将在副本上完成。