Iterator – Design Patterns in Swift

Introduction

The Iterator pattern solves two problems. One is the elements of an aggregate object should be accessed and traversed without exposing its representation (data structures). The other is new traversal operations should be defined for an aggregate object without changing its interface.

Iterator Pattern – UML

Use Iterator Pattern in Swift

This is a collection that we’re going to iterate through using an iterator derived from IteratorProtocol.

class WordsCollection {

    fileprivate lazy var items = [String]()

    func append(_ item: String) {
        self.items.append(item)
    }
}

extension WordsCollection: Sequence {

    func makeIterator() -> WordsIterator {
        return WordsIterator(self)
    }
}

Concrete Iterators implement various traversal algorithms. These classes store the current traversal position at all times.

class WordsIterator: IteratorProtocol {

    private let collection: WordsCollection
    private var index = 0

    init(_ collection: WordsCollection) {
        self.collection = collection
    }

    func next() -> String? {
        defer { index += 1 }
        return index < collection.items.count ? collection.items[index] : nil
    }
}

This is another collection that we’ll provide AnyIterator for traversing its items.

class NumbersCollection {

    fileprivate lazy var items = [Int]()

    func append(_ item: Int) {
        self.items.append(item)
    }
}

extension NumbersCollection: Sequence {

    func makeIterator() -> AnyIterator<Int> {
        var index = self.items.count - 1

        return AnyIterator {
            defer { index -= 1 }
            return index >= 0 ? self.items[index] : nil
        }
    }
}

Client does not know the internal representation of a given sequence.

class Client {

    static func clientCode<S: Sequence>(sequence: S) {
        for item in sequence {
            print(item)
        }
    }
}

Let’s see how it all works together.

class IteratorConceptual: XCTestCase {

    func testIteratorProtocol() {

        let words = WordsCollection()
        words.append("First")
        words.append("Second")
        words.append("Third")

        print("Straight traversal using IteratorProtocol:")
        Client.clientCode(sequence: words)
    }

    func testAnyIterator() {

        let numbers = NumbersCollection()
        numbers.append(1)
        numbers.append(2)
        numbers.append(3)

        print("\nReverse traversal using AnyIterator:")
        Client.clientCode(sequence: numbers)
    }
}

Conclusion

In summary, Iterator is easy to recognize by the navigation methods (such as nextprevious and others). Client code that uses iterators might not have direct access to the collection being traversed. The pattern is very common in Swift code. Many frameworks and libraries use it to provide a standard way for traversing their collections.

Leave a Reply

Your email address will not be published. Required fields are marked *