
Iterator を Swift で
Iterator は、 振る舞いに関するデザインパターンの一つで、 複雑なデータ構造の内部の詳細を公開することなく、 順次横断的に探索することを可能とします。
Iterator のおかげで、 クライアントは、 異なるコレクション上の要素の探索を、 単一のイテレーター・インターフェースを使用して同様の方法で行えます。
複雑度:
人気度:
使用例: このパターンは、 Swift コードではよく見かけます。 多くのフレームワークやライブラリーがこれを使用してコレクション上の探索の標準的方法を提供します。
見つけ方: Iterator は、 next
や previous
などの操舵用メソッドの存在から簡単に識別できます。 イテレーターを使ったクライアント・コードには、 探索対象のコレクションへの直接のアクセスがないかもしれません。
以下の例は Swift Playgroundsで利用できます。
Playgroundバージョンを作成してくれた Alejandro Mohamadに感謝します。
概念的な例
この例は、 Iterator デザインパターンの構造を説明するためのものです。 以下の質問に答えることを目的としています:
- どういうクラスからできているか?
- それぞれのクラスの役割は?
- パターンの要素同士はどう関係しているのか?
ここでパターンの構造を学んだ後だと、 これに続く、 現実世界の Swift でのユースケースが理解しやすくなります。
Example.swift: 概念的な例
import XCTest /// This is a collection that we're going to iterate through using an iterator /// that conforms to 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: 実行結果
Straight traversal using IteratorProtocol: First Second Third Reverse traversal using AnyIterator: 3 2 1
現実的な例
Example.swift: 現実的な例
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?.preOrder(body) right?.preOrder(body) } private func postOrder(_ body: Block) { left?.postOrder(body) right?.postOrder(body) body(value) } }
Output.txt: 実行結果
Tree traversal: Inorder 2 1 3 Tree traversal: Preorder 1 2 3 Tree traversal: Postorder 2 3 1