[swift unboxed]

Safely unboxing the Swift language & standard library


IteratorOverOne

Start at startIndex, iterate one step forward, and you’ve learned the secrets of IteratorOverOne.

30 November 2016 ∙ Standard Library ∙ written by

Remember CollectionOfOne? Our good friend, the simple collection type that holds a single element?

There’s one more important thing you can do with collection types: iterate over them. CollectionOfOne has a corresponding IteratorOverOne that we’ll dig into for this short follow-up post.

Iterators

Sequences and collections in Swift must have a makeIterator() method, which CollectionOfOne defines as follows:

// CollectionOfOne.swift
public func makeIterator() -> IteratorOverOne<Element> {
  return IteratorOverOne(_elements: _element)
}

makeIterator() has to return something that conforms to IteratorProtocol, which we hope IteratorOverOne does. What’s that protocol all about?

IteratorProtocol

You’ll find the declaration of IteratorProtocol in Sequence.swift. It defines a single method requirement:

mutating func next() -> Element?

That’s a mutating method next() that returns an optional. During iteration, something will keep calling next() and if there’s a “next” value then you return it; if you’ve reached the end of the sequence, you return nil.

This means the iterator has to maintain its own state to keep track of where in the sequence it is. That’s why the method is mutating — so it can update its properties if needed.

IteratorOverOne

IteratorOverOne is defined in CollectionOfOne.swift and as usual, the source code will be all inline below. Let’s start with the type declaration:

public struct IteratorOverOne<Element> : IteratorProtocol, Sequence {

As with CollectionOfOne, IteratorOverOne is a struct that takes a generic type parameter specifying the type of thing it returns.

As previously discussed, it conforms to IteratorProtocol. Perhaps a little surprising: iterators are themselves sequences and conform to Sequence. According to the documentation, sequences are “a list of values that you can step through one at a time,” which sound an awful lot like iterators too.

There’s one bit of storage in IteratorOverOne:

internal var _elements: Element?

In CollectionOfOne, the storage property was non-optional because there’s always a single item present. Since the iterator eventually has to return nil to signal the end of the sequence, _elements here is an optional.

You already saw the initializer in action, and here’s how it’s defined:

public init(_elements: Element?) {
  self._elements = _elements
}

It simply takes an Element and stores it in the property. Note that since it takes an optional, IteratorOverOne could also act as an iterator over nothing if you pass nil into the initializer.

Finally, it’s the meat of the iterator: the next() method. Remember, this returns something of type Element, or nil to signal the end of the sequence.

public mutating func next() -> Element? {
  let result = _elements
  _elements = nil
  return result
}

First, we store away the current _elements value into result and hold onto it to return later.

Next, set _elements to nil. That means the next time someone calls next(), we’ll return nil. This is what guarantees the iterator will return at most a single element.

Finally, return result.

You can see that the first time you call next(), you’ll get whatever was passed into the initializer. Subsequent calls to next() will always return nil.

The Closing Brace

IteratorOverOne is a great example of a complex concept in a simple form.

A fuller-featured iterator would probably need more storage and maintain some kind of index into that storage. Or perhaps it would be more like a generator and provide a stream of random numbers or the Fibonacci sequence.

You saw the requirements of IteratorProtocol right there in the source code, but the conformance to Sequence is still a bit of a mystery — where are the required methods and properties for that protocol? As with CollectionOfOne, many things are handled for you automatically thanks to protocol extensions and you get all that sequence-ish behavior “for free”.

Sequence is a bit magical now, isn’t it — we got valid protocol conformance with seemingly no code? 🤔

}