while loops (and do while loops) have their predicates evaluated on each iteration.
for loops evaluate the sequences once.
Here's is a demonstration:
var array: [Int]
print("Test Case 1 - while i < array.count")
array = [1, 2, 3, 4, 5, 6]
var i = 0
while i < array.count {
print(array[i])
if i < 3 { array.append(123) }
i += 1
}
print("\r\nTest Case 2 - for i in array.indices")
array = [1, 2, 3, 4, 5, 6]
for i in array.indices {
print(array[i])
if i < 3 { array.append(123) }
}
print("\r\nTest Case 3 - for i in 0 ..< array.count")
array = [1, 2, 3, 4, 5, 6]
for i in 0 ..< array.count {
print(array[i])
if i < 3 { array.append(123) }
}
Test Case 1 - while i < array.count
1
2
3
4
5
6
123
123
123
Test Case 2 - for i in array.indices
1
2
3
4
5
6
Test Case 3 - for i in 0 ..< array.count
1
2
3
4
5
6
Further details
for loops are just syntactic sugar for easily creating an iterator and iterating over it.
This for loop:
let myArray = [1, 2, 3, 4, 5]
for element in myArray {
print(element)
}
behaves as if you wrote:
let myArray = [1, 2, 3, 4, 5]
let _myArrayIterator = myArray.makeIterator()
while let element = _myArrayIterator.next() {
print(element)
}
The specific kind of iterator being made depends on the sequence being iterated. In the case of Array, it uses an IndexingIterator (source). The default behaviour of Collections that use IndexingIterators is to construct those iterators with a copy of self (source). When this happens, any further changes to self aren't reflecting in the iterator. It holds onto its own independent copy, so any changes to the size of your array aren't reflected in its independent copy.
You can try this for yourself, by making your own Sequence and Iterator which logs what's happening:
/// A simple wrapper around an array, which prints what's happening
struct ArrayWrapper<Element> {
var elements: [Element]
}
extension ArrayWrapper: Sequence {
func makeIterator() -> MyIndexingIterator<Self> {
print("ArrayWrapper.makeIterator() - The `for` loop made an iterator, copying the wrapped array\n")
return MyIndexingIterator(
elements: self, // A *copy* of `self` is given to the new iterator
position: self.startIndex
)
}
}
extension ArrayWrapper: Collection { // Forward all the collection requirements to the real underlying array
var startIndex: Int { elements.startIndex }
var endIndex: Int { elements.endIndex }
func index(after i: Int) -> Int { elements.index(after: i) }
subscript(index: Int) -> Element {
get { elements[index] }
set { elements[index] = newValue }
}
}
struct MyIndexingIterator<Elements: Collection>: IteratorProtocol {
let elements: Elements
var position: Elements.Index
mutating func next() -> Elements.Element? {
print("ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator")
if position == elements.endIndex {
print("\t- Position (\(position)) reached the endIndex (\(elements.endIndex)), so there are no more elements.")
return nil
}
let element = elements[position]
print("\t- Found \"\(element)\" at position \(position)")
let positionBefore = position
elements.formIndex(after: &position) // Advance the position by one
print("\t- Advancing the position from \(positionBefore) -> \(position)")
return element
}
}
let myArray = ArrayWrapper(elements: ["A", "B", "C", "D", "E"])
print("Before the `for` loop\n")
for element in myArray {
print("`for` loop body - printing \"\(element)\"\n")
}
print("\nAfter the `for` loop")
prints:
Before the `for` loop
ArrayWrapper.makeIterator() - The `for` loop made an iterator, copying the wrapped array
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Found "A" at position 0
- Advancing the position from 0 -> 1
`for` loop body - printing "A"
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Found "B" at position 1
- Advancing the position from 1 -> 2
`for` loop body - printing "B"
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Found "C" at position 2
- Advancing the position from 2 -> 3
`for` loop body - printing "C"
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Found "D" at position 3
- Advancing the position from 3 -> 4
`for` loop body - printing "D"
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Found "E" at position 4
- Advancing the position from 4 -> 5
`for` loop body - printing "E"
ArrayWrapper.next() - The `for` loop is pulling the next element from the iterator
- Position (5) reached the endIndex (5), so there are no more elements.
After the `for` loop
for idx in array.indicesorfor elem in array. – Evenfor i in 0..<array.countwould evaluate the count only once.array.countis evaluated each time because you just might be adding and/or removing objects from the array inside thewhileloop.whileand "no" in aforloop