Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 11.5, and Swift 5.2.4
- You will be expected to be aware of how to make a Single View Application in Swift.
- We use the defer keyword in this article
Design Pattern: a general, reusable solution to a commonly occurring problem Iterator: In Swift, a protocol that allows you to loop through a sequence. That is, the protocol supplies values one at a time IteratorProtocol: A protocol that provides the values of a sequence one at a time Sequence: A Protocol that provides sequenced, iterative access to elements of the sequence
I've previously published a rather nice article covering the IteratorProtocol and their use in Linked Lists. If you've knowledge of Linked Lists I'd recommend you read that article now, if not let us press on with this article!
The iterator pattern solves the problem of how we might sequentially traverse a collection of objects.
Swift helps us out by containing the Sequence
protocol. In Swift collections conform to the Sequence
protocol, meaning that we can traverse the elements of the collection.
We may well be familiar with traversing through a collection. In this example, an array of Strings can be traversed with a for...in a loop
let capitals = ["London", "New Delhi", "Naypyitaw", "Kathmandu"]
for capital in capitals {
print (capital)
}
which outputs the following:
London
New Delhi
Naypyitaw
Kathmandu
what is actually happening is makeIterator()
is called, that is the following:
var capitalIterator = capitals.makeIterator()
for capital in capitalIterator {
print (capital)
}
Which gives the same output as before.
Adding Sequence
To add sequence conformance to a type, makeIterator()
needs to return an iterator.
We can step through Apple's example to get a grip on what is happening here.
We create a CountDown
struct that, well, counts down.
struct CountDownIterator: IteratorProtocol {
var current: Int
mutating func next() -> Int? {
if current == 0 {
return nil
} else {
defer { current -= 1 }
return current
}
}
}
struct CountDown: Sequence {
var count: Int
func makeIterator() -> some IteratorProtocol {
return CountDownIterator(current: count)
}
}
Which is called by:
let threeToGo = CountDown(count: 3)
for i in threeToGo {
print (i)
}
Which then displays the countdown to the console:
3
2
1
Now in order to understand the code we need to accept that defer
runs after the leave the current scope of the running function, for an explanation of this look here.
The Sequence
protocol requires that we implement func makeIterator() -> some IteratorProtocol
, and in turn that required we produce an object that conforms to IteratorProtocol
(which itself requires that we implement mutating func next() -> Int?
Yet we can do better!
We can choose to make CountDown
conform to both the Sequence
and IteratorProtocol
, making the sequence serve as its own iterator and giving the following result:
struct Countdown: Sequence, IteratorProtocol {
var count: Int
mutating func next() -> Int? {
if count == 0 {
return nil
} else {
defer { count -= 1 }
return count
}
}
}
let threeToGo = Countdown(count: 3)
for i in threeToGo {
print(i)
}
which of course writes out the same result to the console:
3
2
1
Using an iterator may be less efficient than going through the elements directly, although a sequence should provide an iterator in O(1), and traversing usually takes place in O(n).
A protocol that allows you to move through a sequence? That sounds like IteratorProtocol, and this article has gone some way to explain the use of such iterators in Swift. I hope this has been of some help to you.
If you've any questions, comments or suggestions please hit me up on Twitter