Chr*_*phe 6 iteration collections language-lawyer for-in-loop swift
我正在尝试使用循环对数组进行迭代for .. in ..。我的问题与在循环体内更改集合的情况有关。
似乎迭代是安全的,即使列表在此期间缩小。所述for迭代变量依次取的值(索引和)那些已经在循环的开始的数组中的元素,尽管在流所做的更改。例子:
var slist = [ "AA", "BC", "DE", "FG" ]
for (i, st) in slist.enumerated() { // for st in slist gives a similar result
print ("Index \(i): \(st)")
if st == "AA" { // at one iteration change completely the list
print (" --> check 0: \(slist[0]), and 2: \(slist[2])")
slist.append ("KLM")
slist.insert(st+"XX", at:0) // shift the elements in the array
slist[2]="bc" // replace some elements to come
print (" --> check again 0: \(slist[0]), and 2: \(slist[2])")
slist.remove(at:3)
slist.remove(at:3)
slist.remove(at:1) // makes list shorter
}
}
print (slist)
Run Code Online (Sandbox Code Playgroud)
这非常有效,[ "AA", "BC", "DE", "FG" ]即使在第一次迭代后数组完全更改为对值进行迭代["AAXX", "bc", "KLM"]
我想知道我是否可以放心地依赖这种行为。不幸的是,语言指南没有说明在修改集合时迭代集合的任何信息。该for .. in部分也没有解决这个问题。所以:
IteratorProtocol的文档说:“每当您对数组、集合或任何其他集合或序列使用 for-in 循环时,您\xe2\x80\x99 就在使用该类型\xe2\x80\x99s 迭代器。” 因此,我们保证for in将使用.makeIterator()和分别.next()最常见地定义在Sequence和上的循环IteratorProtocol。
Sequence的文档称,“该Sequence协议对符合类型是否会被迭代破坏性消耗没有要求。” 因此,这意味着 a 的迭代器Sequence不需要制作副本,因此我认为在迭代序列时修改序列通常是不安全的。
同样的警告不会出现在Collection的文档中,但我也不认为迭代器会生成副本,因此我不认为在迭代集合时修改集合通常是,安全的。
\n但是,Swift 中的大多数集合类型都是struct具有值语义或写时复制语义的。我不太确定这方面的文档在哪里,但这个链接确实说“在 Swift 中,Array、String、 和Dictionary都是值类型......你不需要做任何特殊的 \xe2\ x80\x94,例如制作显式副本 \xe2\x80\x94 以防止其他代码在您背后修改该数据。” 特别是,这意味着 for Array,.makeIterator() 无法保存对数组的引用,因为 for 迭代器Array不必“做任何特殊的事情”来防止其他代码(即您的代码)修改它保存的数据。
我们可以更详细地探讨这一点。Iterator的类型Array定义为type IndexingIterator<Array<Element>>。文档IndexingIterator说它是集合迭代器的默认实现,因此我们可以假设大多数集合都会使用它。我们可以在源代码IndexingIterator中看到它保存了一个副本集合的
@frozen\npublic struct IndexingIterator<Elements: Collection> {\n @usableFromInline\n internal let _elements: Elements\n @usableFromInline\n internal var _position: Elements.Index\n\n @inlinable\n @inline(__always)\n /// Creates an iterator over the given collection.\n public /// @testable\n init(_elements: Elements) {\n self._elements = _elements\n self._position = _elements.startIndex\n }\n ...\n}\nRun Code Online (Sandbox Code Playgroud)\n默认情况下.makeIterator()只是创建此副本。
extension Collection where Iterator == IndexingIterator<Self> {\n /// Returns an iterator over the elements of the collection.\n @inlinable // trivial-implementation\n @inline(__always)\n public __consuming func makeIterator() -> IndexingIterator<Self> {\n return IndexingIterator(_elements: self)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n尽管您可能不想信任此源代码,但库演化文档声称“该@inlinable属性是库开发人员的承诺,即函数的当前定义在与库的未来版本一起使用时将保持正确”,@frozen并且意味着 的成员IndexingIterator不能改变。
总而言之,这意味着任何具有值语义和 an 的集合类型IndexingIterator在使用 using 循环时Iterator 都必须进行复制for in(至少直到下一个 ABI 中断,这应该是一个很长的路要走)。即便如此,我也不认为苹果可能会改变这种行为。
综上所述
\n我不知道文档中明确说明的任何地方“您可以在迭代数组时修改数组,并且迭代将像您制作副本一样进行”,但这也是一种可能不应该写下来的语言,因为这样的代码肯定会让初学者感到困惑。
\n然而,有足够的文档表明循环for in只是调用.makeIterator(),并且对于具有值语义和默认迭代器类型(例如,Array)的任何集合,.makeIterator()都会创建一个副本,因此不会受到循环内代码的影响。此外,因为Array和一些其他类型,如Set和Dictionary写时复制的,所以在循环内修改这些集合将产生一次性复制惩罚,因为循环体不会对其存储具有唯一的引用(因为迭代器将也有参考意义)。如果您没有对存储的唯一引用,这与在循环外修改集合所产生的惩罚完全相同。
如果没有这些假设,就无法保证您的安全,但在某些情况下您仍然可能拥有安全。
\n编辑:
\n我刚刚意识到我们可以创建一些对序列不安全的情况。
\nimport Foundation\n\n/// This is clearly fine and works as expected.\nprint("Test normal")\nfor _ in 0...10 {\n let x: NSMutableArray = [0,1,2,3]\n for i in x {\n print(i)\n }\n}\n\n/// This is also okay. Reassigning `x` does not mutate the reference that the iterator holds.\nprint("Test reassignment")\nfor _ in 0...10 {\n var x: NSMutableArray = [0,1,2,3]\n for i in x {\n x = []\n print(i)\n }\n}\n\n/// This crashes. The iterator assumes that the last index it used is still valid, but after removing the objects, there are no valid indices.\nprint("Test removal")\nfor _ in 0...10 {\n let x: NSMutableArray = [0,1,2,3]\n for i in x {\n x.removeAllObjects()\n print(i)\n }\n}\n\n/// This also crashes. `.enumerated()` gets a reference to `x` which it expects will not be modified behind its back.\nprint("Test removal enumerated")\nfor _ in 0...10 {\n let x: NSMutableArray = [0,1,2,3]\n for i in x.enumerated() {\n x.removeAllObjects()\n print(i)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这是 an 的事实NSMutableArray很重要,因为该类型具有引用语义。由于NSMutableArray符合Sequence,我们知道在迭代序列时改变序列是不安全的,即使使用.enumerated()。