Iterator es un patrón de diseño de comportamiento que permite el recorrido secuencial por una estructura de datos compleja sin exponer sus detalles internos.
Gracias al patrón Iterator, los clientes pueden recorrer elementos de colecciones diferentes de un modo similar, utilizando una única interfaz iteradora.
Ejemplo conceptual
Este ejemplo ilustra la estructura del patrón de diseño Iterator y se centra en las siguientes preguntas:
¿De qué clases se compone?
¿Qué papeles juegan esas clases?
¿De qué forma se relacionan los elementos del patrón?
Después de conocer la estructura del patrón, será más fácil comprender el siguiente ejemplo basado en un caso de uso real de Swift.
Example.swift: Ejemplo conceptual
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: Resultado de la ejecución
Straight traversal using IteratorProtocol:
First
Second
Third
Reverse traversal using AnyIterator:
3
2
1
Ejemplo del mundo real
Example.swift: Ejemplo del mundo real
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: Resultado de la ejecución
Tree traversal: Inorder
2
1
3
Tree traversal: Preorder
1
2
3
Tree traversal: Postorder
2
3
1