I am attempting to understand how Swift handles for-in loops.
Overview: we are iterating over the rows of an NSOutlineView
. If a condition is met, we expand the item, which obviously changes the overall row count of the outlineView.
Pre-Condition: the OutlineView has 5 "root" items. Each of those has 5 child items.
final class anOutlineView: NSOutlineView
{
override func reloadData()
{
super.reloadData()
for i in 0 ..< self.numberOfRows
{
// Assume we expand the item at row 0, which increases
// the overall outlineView row count from 5 to 10.
}
}
}
In this approach, the loop stops when i == 4
. I assume that's because Swift evaluates the range only once, the first time it encounters it? Is there a way to change that behavior so that the conditions are re-evaluated each time through the loop, like a traditional for
loop?
Replacing the for
loop with a while
loop obviously works and is a fine solution. I'm simply trying to understand the nuances of Swift because this behavior is not what I expected. In Objective-C, the for
loop conditions were evaluated on each iteration and it was a well-known performance optimization to refrain from calling self.property
in loop conditions (unless a good reason existed, as it does in this case.)
0 ..< self.numberOfRows
is a Range
and in particular a Sequence
. Iterating over a sequence is done by creating an iterator, and then calling its next()
method until the iterator is exhausted, compare IteratorProtocol
:
Whenever you use a for-in loop with an array, set, or any other collection or sequence, you’re using that type’s iterator. Swift uses a sequence’s or collection’s iterator internally to enable the for-in loop language construct.
So
for i in 0 ..< self.numberOfRows {
...
}
is equivalent to
let range = 0 ..< self.numberOfRows
var it = range.makeIterator()
while let i = it.next() {
...
}
Modifying numberOfRows
during the iteration does not mutate the range (which is a value type) or the iterator, and therefore does not affect the number of iterations.