Working with collections is one of every-day-tasks for most developers.

Sequence

We are all familiar with collection types. Array is, probably, the most common way to represent collection of items. We can easily iterate through by using for loop.

let array = [2, 3, 5, 7, 11, 13, 17]
for i in 0..<array.count {
  print(array[i])
}

Another Swift collections representations, like Dictionary, Set and others has one important thing in common: all of them are adopting Sequence protocol. We can create custom sequences for very wide range of purposes. It can be either finite or inifinite sequence. For instance, we may need the powers of 2 sequence or similar.

A sequence is a list of values that you can step through one at a time. The most common way to iterate over the elements of a sequence is to use a for-in loop

Sequence protocol has three associated types:

It also has useful methods for getting subsequences and iteration.

public protocol Sequence {

    /// A type representing the sequence's elements.
    associatedtype Element where Self.Element == Self.Iterator.Element, Self.Iterator.Element == Self.SubSequence.Element, Self.SubSequence.Element == Self.SubSequence.Iterator.Element

    /// A type that provides the sequence's iteration interface and
    /// encapsulates its iteration state.
    associatedtype Iterator : IteratorProtocol

    /// A type that represents a subsequence of some of the sequence's elements.
    associatedtype SubSequence : Sequence = AnySequence<Self.Element> where Self.SubSequence == Self.SubSequence.SubSequence

    /// Returns an iterator over the elements of this sequence.
    public func makeIterator() -> Self.Iterator
    ...

The makeIterator() -> Self.Iterator method returns an iterator over the elements of the sequence. Where Iterator is a type that provides the sequence’s iteration interface and encapsulates its iteration state.

IteratorProtocol

IteratorProtocol is a simple protocol, which contains one associated type and one method - next()

public protocol IteratorProtocol {
   associatedtype Element
   public mutating func next() -> Self.Element?
}

Element is a type of an element generated by self. next() returns an element if it exists, otherwise returns nil.

I will demonstrate Sequence and Iterator power by creating some convenience methods for Date.

Disclaimer: Calendar and Date are complex classes (or class clusters). Working with them, you need to take in concideration a lot of boilerplates, like time zone calculations, device locale and other important calculations. This article omits most of them for the sake of brevity. Samples may be far-fetched for the demonstration purposes

Case study: Spanish lessons schedule

Let’s imagine, that we want to learn new language. Spanish, for example. Your professor told you that you will have one lesson each N days starting from today. But the bad thing – your professor doesn’t want to work on Sundays. So if the lesson is on Sunday, it will be rescheduled to the next day. We want to receive the schedule – Array<Date> for next M days.

Before we go

Lets start from adding some convenience functions to Date. First of all, we will extend Int with calendarUnit(:) function. This function returns DateComponents object depending on given Calendar.Component. For the sake of convenience, we will add only days (that’s enough for our example, but you can add the same for all calendar units you need).

extension Int {
    public var days: DateComponents {
        return calendarUnit(unit: .day)
    }
    
    private func calendarUnit(unit: Calendar.Component) -> DateComponents {
        var dateComponents = DateComponents()
        dateComponents.setValue(self, for: unit)
        return dateComponents
    }
}

Next we will write small Date extension. For our convenience we will wrap Date to be able to get the value of the needed Calendar.Component.

let calendar = Calendar.current

extension Date {
    private func valueForUnit(unit: Calendar.Component) -> Int {
        return calendar.component(unit, from: self)
    }
    
    var weekday: Int {
        return valueForUnit(unit: .weekday)
    }
}

print(Date().weekday)
// > prints current weekday index (1..7, where 1 is Sunday)

Then we define function + to have ability to add date components to our NSDate:

func + (date: Date, component: DateComponents) -> Date {
    if let date = calendar.date(byAdding: component, to: date) {
        return date
    }
    fatalError()
}

Now we are able to perform calculation like this:

let date = Date()
let nextDay = date + 3.days

Schedule generator

DateIterator looks like:

class DateIterator: IteratorProtocol {
    // We are using naive approach here
    // It shouldn't be used in real project
    var dayOffWeekday: Int = 1 // Sunday
    var N: Int = 3

    private(set) var lessonsCount: Int
    private(set) var startDate: Date

    private var numIterations = 0

    init(_ start: Date, _ numLessons: Int) {
        lessonsCount = numLessons
        startDate = start
    }

    func next() -> Date? {
        guard numIterations < lessonsCount else {
            return nil
        }
        numIterations += 1

        var next = startDate + N.days
        if next.weekday == dayOffWeekday {
            next = next + 1.days
        }
        startDate = next
        return next
    }
}

let dg = DateIterator(Date(), 10)
dg.N = 1
dg.dayOffWeekday = 1
while let date = dg.next() {
    print(date)
}

As you see in the code sample above, in some point our generator returns nil. It is important! If you remove nil termination, generator will continue to produce values while your computer has free memory.

Another important thing is: if you try to iterate through dg one more time, it will not work as expected, because the instance of the generator is already exhausted.

while let date = dg.next() { // Exhausted
    print(date)
}
// does nothing. 

We implemented DateIterator to build a sequence on it.

class DateSequece: Sequence {
    typealias Element = Date
    typealias Iterator = DateIterator
    
    private var lessonsCount: Int
    private var startDate: Date

    init(_ start: Date, _ numLessons: Int){
        lessonsCount = numLessons
        startDate = start
    }

    func makeIterator() -> Iterator {
        return DateIterator(startDate, lessonsCount)
    }
}

As you see, sequence code is very simple and understandable. Actually, our Spanish schedule problem is solved :)

// Contains needed dates for the next 10 lessons
let sequence = Array(DateSequece(Date(), 10))

Apple guys are smart, and for the most cases you can use AnyIterator. Rewriten with AnyIterator, our sequence looks like this:

class SimpleDateSequece: Sequence {
    typealias Element = Date
    typealias Iterator = AnyIterator<Element>
    
    var lessonsCount: Int
    var startDate: Date
    var daysStep: Int = 1
    private var numIterations = 0

    init(_ start: Date, _ numLessons: Int, step: Int){
        lessonsCount = numLessons
        startDate = start
        daysStep = step
    }

    func makeIterator() -> AnyIterator<Date> {
        return AnyIterator({ () -> Date? in
            guard self.numIterations < self.lessonsCount else {
                return nil
            }
            self.numIterations += 1
            
            let next = self.startDate + self.daysStep.days
            self.startDate = next
            return next
        })
    }
}

let simpleSequence = Array(SimpleDateSequece(Date(), 5, step: 4))

Infinite sequences

Remember, we were afraid to remove nil termination? This is the time to do it:

class InfiniteDateGenerator: IteratorProtocol {
    var startDate: Date

    private var numIterations = 0

    init(_ start: Date) {
        startDate = start
    }

    func next() -> Date? {
        let next = startDate + 1.days
        startDate = next
        return next
    }
}

class InfiniteDateSequece: Sequence {
    typealias Element = Date
    typealias Iterator = InfiniteDateGenerator
    
    private var startDate: Date

    init(_ start: Date){
        startDate = start
    }

    func makeIterator() -> InfiniteDateGenerator {
        return InfiniteDateGenerator(startDate)
    }
}

let infiniteSequence = Array(InfiniteSequence(Date()))

If you are using a playground, then you can see an infinite loop right after you hit Cmd+S shortcut. No worries, we can get needed amount of elements from the infinite sequence:

let tenFirsElements = Array(InfiniteDateSequece(Date()).prefix(10))

One of the key benefit of infinite sequences is ability to modify all it’s elements according to the rule. For instance, i want first 10 dates of our infinite sequence, but I hate Mondays. So I want to throw Mondays away:

let mondayIndex = 2
let noMondaysSequence = Array(InfiniteDateSequece(Date()).filter({$0.weekday != mondayIndex}).prefix(10) ) // Infinite

Surprizingly, but this approach will not work, because we have infinite sequence. LazySequence is the same as Sequence, with the key difference, that the elements of the result function, such as map, filter, e. t. c. are computed on-demand as the result is used. To make sequence lazy use the lazy property of the sequence:

let lazyNoMondaysSequence = Array(InfiniteDateSequece(Date()).lazy.filter({$0.weekday != mondayIndex}).prefix(10) ) //

Voila! We have finite dates sequence without Mondays.

Conclusion

Sequence and Iterator are one of the most used protocols. It is correct and elegant solution for dealing with difficult data collections generating and iteration.

Further reading