top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

Iterator Pattern: Protocol Oriented Design Pattern


The Iterator pattern is a behavior design pattern that enables us to iterate or traverse the elements of a collection without exposing how those elements are stored.


Problem We Are Trying To Solve

We have a custom data structure or collection and we would like the ability to traverse the collection in a way that other developers are use too. We would also like to do it in a way that makes sense for the language we are working in, Swift in our case. We also do not want to expose the underlying data storage mechanism.


Our Solution

The solution that we will present is to use the Swift’s iterator and sequence protocols to give us the ability to traverse our custom data structures using the standard for-in loop. This will also give us access to other common operations that depends on sequential access, like the contains() function


The Example

In this example, we are going to create a queue type but we would also like to be able to iterate over the values of the queue in order to see what items it contains without removing the items. We could use this for almost any data structure like an linked list, stacks or heaps.



As usual in our design pattern tutorials, we are going to be showing the minimal amount of code necessary to demonstrate this pattern because we want the focus of this post to be on how the observer design pattern works and not on implementing the example. In a real queue implementation, we would probably want to implement copy-on-write for the queue however for the example here we are going to forgo that to focus on the design pattern. If you would like to read about how to implement copy-on-write, you can ready our blog post on it


As a twist, we will not be starting our design with the protocol, instead in this pattern we will be adopting two protocols, that Swift provides, in order to provide the functionality that we would like. We will start off by creating a real simple queue type.

struct Queue<T> {
  private var elements = [T]()

  mutating func enqueue(_ value: T) {
    elements.append(value)
  }

  mutating func dequeue() -> T? {
    guard !elements.isEmpty else {
      return nil
    }
    return elements.removeFirst()
  }
}

This is just a very basic generic queue implementation that we should be familiar with. If you are not familiar with generics you can read our post about them.


In order to adopt the sequence protocol, the first thing we are going to do is to create an iterator for our type. We will name this type QueueIterator and we will need it to adopt the IteratorProtocol.

struct QueueIterator<T>: IteratorProtocol {
    private var current = 0
    private let queueData: [T]

    init(_ queueData: [T]) {
        self.queueData = queueData
    }

    mutating func next() -> T? {
        defer { current += 1 }
        return queueData.count > current ? queueData[current] : nil
    }
}

In order for our type to conform to the IteratorProtocol we must implement the next() function. This function is used to get the next element in the sequence as we traverse the collection. For our Queue type, since we are using an array to store our data, we just get the next element in the array and once we reach the end of the array we return nil.


We also created an initiator which will create a copy of the queue’s data which will be used to iterate over. In other data structures we may be passing in something else besides an array. As an example, for a linked list we may pass in the first node of the list.


The last thing we need to do is to extend the Queue type to adopt the Sequence protocol.

extension Queue: Sequence {
    func makeIterator() -> QueueIterator<T> {
        return QueueIterator(self.elements)
    }
}

In order to conform to the Sequence protocol we must provide a makeIterator() function which will return an instance of a type that adopts the IteratorProtocol protocol which in our case will be the QueueIterator type.


Once we create a type that adopts Swift’s built in IteratorProtocol protocol and then make our type conform to the Sequeunce protocol, we can traverse the items in the data structure, using the standard for..in loop like this:

var myQueue = Queue<String>()
myQueue.enqueue("Hello")
myQueue.enqueue("World")
myQueue.enqueue("My")
myQueue.enqueue("Name")
myQueue.enqueue("Is")
myQueue.enqueue("Jon")

for word in myQueue {
    print(word)
}

The biggest advantage that we have by using the IteratorProtocol and Sequence protocols is we are able to traverse our data structure or collection in a manner that is familiar to other Swift developers. This makes it very easy for other developers to understand and use our custom collections.


masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt

bottom of page