Usage examples: The pattern is very common in Swift code. Many frameworks and libraries use it to provide a standard way for traversing their collections.
Identification: Iterator is easy to recognize by the navigation methods (such as next, previous and others). Client code that uses iterators might not have direct access to the collection being traversed.
This example illustrates the structure of the Iterator design pattern and focuses on the following questions:
What classes does it consist of?
What roles do these classes play?
In what way the elements of the pattern are related?
After learning about the pattern’s structure it’ll be easier for you to grasp the following example, based on a real-world Swift use case.
Example.swift: Conceptual example
import XCTest
/// 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)
}
}
Output.txt: Execution result
Straight traversal using IteratorProtocol:
First
Second
Third
Reverse traversal using AnyIterator:
3
2
1
Real World Example
Example.swift: Real world example
import XCTest
class IteratorRealWorld: XCTestCase {
func test() {
let tree = Tree(1)
tree.left = Tree(2)
tree.right = Tree(3)
print("Tree traversal: Inorder")
clientCode(iterator: tree.iterator(.inOrder))
print("\nTree traversal: Preorder")
clientCode(iterator: tree.iterator(.preOrder))
print("\nTree traversal: Postorder")
clientCode(iterator: tree.iterator(.postOrder))
}
func clientCode<T>(iterator: AnyIterator<T>) {
while case let item? = iterator.next() {
print(item)
}
}
}
class Tree<T> {
var value: T
var left: Tree<T>?
var right: Tree<T>?
init(_ value: T) {
self.value = value
}
typealias Block = (T) -> ()
enum IterationType {
case inOrder
case preOrder
case postOrder
}
func iterator(_ type: IterationType) -> AnyIterator<T> {
var items = [T]()
switch type {
case .inOrder:
inOrder { items.append($0) }
case .preOrder:
preOrder { items.append($0) }
case .postOrder:
postOrder { items.append($0) }
}
/// Note:
/// AnyIterator is used to hide the type signature of an internal
/// iterator.
return AnyIterator(items.makeIterator())
}
private func inOrder(_ body: Block) {
left?.inOrder(body)
body(value)
right?.inOrder(body)
}
private func preOrder(_ body: Block) {
body(value)
left?.inOrder(body)
right?.inOrder(body)
}
private func postOrder(_ body: Block) {
left?.inOrder(body)
right?.inOrder(body)
body(value)
}
}
Output.txt: Execution result
Tree traversal: Inorder
2
1
3
Tree traversal: Preorder
1
2
3
Tree traversal: Postorder
2
3
1