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
Queue
struct to theSequence
protocol. - Collection: The
Queue
struct holds the elements and provides themakeIterator()
method. - Client: The
for-in
loop 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
Sequence
rather than implementingIteratorProtocol
directly, asSequence
provides higher-order functions likemap
andfilter
.
In Bullets
- The Iterator Pattern provides a standard way to iterate over a collection.
- Commonly implemented by conforming to Swift's
Sequence
protocol. - Useful for traversing custom collections using the
for-in
syntax.