David David - 1 year ago 85
Swift Question

Remove element from collection during iteration with forEach

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

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

removes an item from

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?

Answer Source

This is indeed expected behaviour – and it's due to the fact that an array in Swift (unlike NSArray) is a value type, meaning that it gets copied when passed about. Therefore when you iterate over an array, be it with forEach or for ... in – you're actually iterating over a copy of that array (although of course the compiler will only make a copy when needed).

This works through using an Iterator (or Generator pre-Swift 3). What's happening behind the scenes when you iterate over a collection looks something like this:

let collection = [1, 2, 3, 4]
var iterator = collection.makeIterator()

while let element = iterator.next() {
    // do something with the element

In the case of an collection, an IndexingIterator is returned for makeIterator() – which will iterate through the elements by keeping track of an index, incrementing it each time you call next(), and returning the element at that given index – until you reach endIndex. (you can see its exact implementation here).

The collection that you're iterating over is kept as a property of IndexingIterator, therefore ensuring that you're working on a copy.

This therefore means that it's perfectly safe to mutate an array while iterating.

I would also note @AMomchilov's comment about using removeItem() within this loop, why not just simply use removeAll()?