Explain the Iterator Pattern in iOS
· 3 min read
Sources & Resources
Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:
TL/DR
The Iterator Pattern provides a standard way to traverse through a collection of objects without exposing the underlying representation. It helps in making custom objects iterable using Swift’s for-in syntax.
Concept Overview​
The Iterator Pattern involves the following key elements:
- Iterator: The object that provides the mechanism to traverse the collection.
- Iterable Collection: The object or data structure that holds multiple elements.
- Client: The code that uses the iterator to loop through the elements.
In Swift, it is common to conform to the Sequence protocol, which itself conforms to IteratorProtocol, and this enables the usage of high-order functions like map, filter, etc., "for free."
How Iterator Works:​
- Iterator: Provides a method to get the next element from a collection.
- Collection: Creates an iterator to traverse its elements.
- Client: Uses the iterator to access elements in sequence.
Key Benefits:​
- Encapsulation: Hides the underlying structure of the collection from the client.
- Standardization: Provides a common way to traverse different types of collections.
Playground Example​
Here is an example of using the Iterator Pattern to build a queue system:
import Foundation
// MARK: - Iterator Collection (Queue)
public struct Queue<T> {
private var array: [T?] = []
private var head = 0
public var isEmpty: Bool {
return count == 0
}
public var count: Int {
return array.count - head
}
public mutating func enqueue(_ element: T) {
array.append(element)
}
public mutating func dequeue() -> T? {
guard head < array.count, let element = array[head] else {
return nil
}
array[head] = nil
head += 1
return element
}
}
// MARK: - Conforming to Sequence Protocol
extension Queue: Sequence {
public func makeIterator() -> IndexingIterator<ArraySlice<T?>> {
let nonEmptyValues = array[head..<array.count]
return nonEmptyValues.makeIterator()
}
}
// Usage Example
var queue = Queue<String>()
queue.enqueue("Task 1")
queue.enqueue("Task 2")
queue.enqueue("Task 3")
for task in queue {
print(task ?? "No Task")
}
How It Works:​
- Iterator: The iterator is created by conforming the
Queuestruct to theSequenceprotocol. - Collection: The
Queuestruct holds the elements and provides themakeIterator()method. - Client: The
for-inloop is used to iterate over the elements in the queue.
When to Use​
- Custom Collections: When you need to make a custom collection of objects iterable using standard Swift syntax.
- Abstraction: When you want to abstract the traversal logic of a complex object from the client.
When to Be Careful​
- IteratorProtocol vs. Sequence: It’s better to conform to
Sequencerather than implementingIteratorProtocoldirectly, asSequenceprovides higher-order functions likemapandfilter.
In Bullets
- The Iterator Pattern provides a standard way to iterate over a collection.
- Commonly implemented by conforming to Swift's
Sequenceprotocol. - Useful for traversing custom collections using the
for-insyntax.
