As with all programming posts, we start with a completely contrived example that makes no sense in the real world.
Say you’re writing ListOfIntegers, which is, well, a list of integers. In the
real world, you’d absolutely use Array<Int> or something similar. Just go with
me on this.
We can start this way:
class ListOfIntegers {
typealias Element = Int
private var backingStore = [Int]()
init() { }
init(_ value: [Int]) {
self.backingStore = value
}
}
At a minimum, you probably want your ListOfIntegers to be a Sequence, so
you can iterate over your list. Thus, you need to provide a makeIterator()
function. This provides the Iterator that will allow Swift to perform that
iteration.
Creating an Iterator seems like a whole lot of work, involving making a whole new
subtype. No thanks. I’m lazy; that’s why I’m a developer.
AnyIterator
This week I discovered a very neat shortcut: AnyIterator<T>. When I first
saw this struct, I thought it was simply there for the purposes of type erasure.
Looking through the class documentation, however, I found this gem:
/// Creates an iterator that wraps the given closure in its next() method.
init(_ body: @escaping () -> Element?)
Wait… what?
If you look at IteratorProtocol, it’s rather simple:
public protocol IteratorProtocol {
/// The type of element traversed by the iterator.
associatedtype Element
/// Advances to the next element and returns it, or
/// `nil` if no next element exists.
mutating func next() -> Self.Element?
}
Suddenly AnyIterator<T>'s init(:)'s comment makes sense:
Creates an iterator that wraps the given closure in its
next()method.
By providing a closure to the init(:), we can provide an implementation for
this Iterator’s next() method. Sweet!
Adding an Iterator to ListOfIntegers
We can leverage this to easily add an Iterator to our ListOfIntegers:
class ListOfIntegers: Sequence {
typealias Element = Int
private var backingStore = [Int]()
init() { }
init(_ value: [Int]) {
self.backingStore = value
}
func makeIterator() -> AnyIterator<Int> {
// We establish the index *outside* the
// closure. More below.
var index = self.backingStore.startIndex
// Note the use of AnyIterator.init(:) with
// trailing closure syntax.
return AnyIterator { () -> Int? in
// Is the current index before the end?
if index < self.backingStore.endIndex {
// If so, get the current value
let currentValue = self.backingStore[index]
// Set a new index for the next execution
index = self.backingStore.index(after: index)
// Return the current value
return currentValue
} else {
// We've run off the end of the array, return nil.
return nil
}
}
}
}
A couple things to note here:
- We’re expressly returning
AnyIterator<Int>instead of the defaultListOfIntegers.Iterator. The latter would require us to have a secondtypealiasto specify the type of theIterator; by being explicit, the compiler can inferListOfIntegers.Iteratorto beAnyIterator<Int>. - When I first wrote this, I made the rookie mistake of creating
var indexwithin the closure. This meant that every time the iterator was asked to move to the next element, instead it just started over. 🤦🏻♂️ Thus, it’s important to create yourindexoutside the closure, so it doesn’t get reset every time you callnext(). - Since we’re using an
Array<Int>as our backing store, a much simpler implementation would be to simply
but that would defeat the purpose of this post.func makeIterator() -> Array<Int>.Iterator { return self.backingStore.makeIterator() }
I don’t often find myself in a situation wherein I need to create a custom
Sequence; much less, a custom Iterator for that Sequence. However, when
I find myself needing a custom Iterator in the future, I’ll certainly start
with AnyIterator<T>.
If you’d like to play with this in a Playground, I’ve put the code up in a gist. Just copy/paste that into a new Playground.