在使用 forEach 迭代期间从集合中删除元素 [英] Remove element from collection during iteration with forEach

查看:28
本文介绍了在使用 forEach 迭代期间从集合中删除元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近很想也没想就写了这段代码:

Recently, I wrote this code without thinking about it very much:

myObject.myCollection.forEach { myObject.removeItem($0) }

其中 myObject.removeItem(_)myObject.myCollection 中删除一个项目.

where myObject.removeItem(_) removes an item from myObject.myCollection.

现在看代码,我很困惑为什么这甚至有效 - 难道我不应该在 Collection is mutated while being numerated 的行中得到一个异常吗?使用常规 for-in 循环时,同样的代码也能工作!

Looking at the code now, I am puzzled as to why this even works - shouldn't I get an exception along the lines of Collection was mutated while being enumerated? The same code even works when using a regular for-in loop!

这是预期的行为还是我幸运"它没有崩溃?

Is this expected behaviour or am I 'lucky' that it isn't crashing?

推荐答案

这确实是预期的行为——这是因为 Swift 中的 Array(以及 Swift 中的许多其他集合)标准库)是一种具有写时复制语义的值类型.这意味着它的底层缓冲区(间接存储)将在发生变异时复制(并且,作为优化,仅当它不是唯一引用时).

This is indeed expected behaviour – and is due to the fact that an Array in Swift (as well as many other collections in the standard library) is a value type with copy-on-write semantics. This means that its underlying buffer (which is stored indirectly) will be copied upon being mutated (and, as an optimisation, only when it's not uniquely referenced).

当您要迭代 Sequence(例如数组)时,无论是使用 forEach(_:) 还是标准的 for in 循环,从序列的 makeIterator() 方法及其next() 方法被重复应用以顺序生成元素.

When you come to iterate over a Sequence (such as an array), be it with forEach(_:) or a standard for in loop, an iterator is created from the sequence's makeIterator() method, and its next() method is repeatedly applied in order to sequentially generate elements.

您可以将迭代序列视为如下所示:

You can think of iterating over a sequence as looking like this:

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
}

对于 ArrayIndexingIterator 用作它的迭代器——它将通过简单地存储该集合以及迭代的当前索引来遍历给定集合的元素.每次 next() 被调用时,基本集合都带有索引下标,然后递增,直到它到达 endIndex(你可以看到它的 此处的具体实现).

In the case of Array, an IndexingIterator is used as its iterator – which will iterate through the elements of a given collection by simply storing that collection along with the current index of the iteration. Each time next() is called, the base collection is subscripted with the index, which is then incremented, until it reaches endIndex (you can see its exact implementation here).

因此,当您在循环中改变数组时,它的底层缓冲区不是唯一引用,因为迭代器也可以查看它.这会强制复制缓冲区 - myCollection 然后使用该副本.

Therefore, when you come to mutate your array in the loop, its underlying buffer is not uniquely referenced, as the iterator also has a view onto it. This forces a copy of the buffer – which myCollection then uses.

所以,现在有两个数组 - 一个正在迭代,另一个正在变异.只要 myCollection 的缓冲区保持唯一引用,循环中的任何进一步变化都不会触发另一个副本.

So, there are now two arrays – the one which is being iterated over, and the one you're mutating. Any further mutations in the loop won't trigger another copy, as long as myCollection's buffer remains uniquely referenced.

因此,这意味着在枚举集合时使用值语义改变集合是完全安全的.枚举将遍历整个集合的长度——完全独立于您所做的任何更改,因为它们将在副本上完成.

This therefore means that it's perfectly safe to mutate a collection with value semantics while enumerating over it. The enumeration will iterate over the full length of the collection – completely independant of any mutations you do, as they will be done on a copy.

这篇关于在使用 forEach 迭代期间从集合中删除元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆