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 secondtypealias
to specify the type of theIterator
; by being explicit, the compiler can inferListOfIntegers.Iterator
to beAnyIterator<Int>
. - When I first wrote this, I made the rookie mistake of creating
var index
within 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 yourindex
outside 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.