Neat Swift Trick: AnyIterator

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:

  1. We’re expressly returning AnyIterator<Int> instead of the default ListOfIntegers.Iterator. The latter would require us to have a second typealias to specify the type of the Iterator; by being explicit, the compiler can infer ListOfIntegers.Iterator to be AnyIterator<Int>.
  2. 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 your index outside the closure, so it doesn’t get reset every time you call next().
  3. Since we’re using an Array<Int> as our backing store, a much simpler implementation would be to simply
    func makeIterator() -> Array<Int>.Iterator { 
        return self.backingStore.makeIterator() 
     }
    
    but that would defeat the purpose of this post.

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.