この記事は何?
SwiftのIteratorProtocolについて調べました。
主に、開発者ドキュメント
より翻訳した内容になります。
Declaration
宣言
protocol IteratorProtocol<Element>
Overview
概要
Swiftにおいて、「連続して、値を一つずつ取り出せるデータ構造」をシーケンスといいます。
Sequence
プロトコルに適合する型のインスタンスはfor-in
ループを使って、要素ごとに処理を反復できます。
その際、シーケンスに対して「一つずつ値を取り出す役割」を果たすインスタンスがイテレータです。
インスタンスの型がIteratorProtocol
プロトコルに適合する場合、makeIterator()
メソッドでイテレータを取得できます。
IteratorProtocol
プロトコルはSequence
プロトコルと密接に関わっています。
イテレータは反復処理を追跡して、シーケンスを進めながら要素を一つずつ返します。
配列、セット、または他のコレクションやシーケンスでfor-in
ループを使用すると、その型のイテレータを使用しています。
Swiftはfor-in
ループを実行する際、シーケンスまたはコレクションのイテレータを内部的に使用します。
シーケンスのイテレータを直接使用すると、for-in
ループを使用して「そのシーケンス上で反復処理する」のと同じ順序で要素にアクセスできます。
以下の例では、for-in
ループを利用して配列の各要素を表示します。
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
print(animal)
}
// Prints "Antelope"
// Prints "Butterfly"
// Prints "Camel"
// Prints "Dolphin"
内部的には、Swiftはanimals
配列のイテレータを使用して配列の構成要素をループしています。
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
print(animal)
}
// Prints "Antelope"
// Prints "Butterfly"
// Prints "Camel"
// Prints "Dolphin"
animals.makeIterator()
メソッドは「配列のイテレータのインスタンス」を返します。
そして、while
ループはイテレータのnext()
メソッドを繰り返し呼び出し、取得した各要素をanimal
にバインドし、next()
メソッドがnil
を返すと終了します。
Using Iterators Directly
イテレータを使用するには
for-in
ループはSwiftでシーケンスのデータを横断するためのより慣用的なアプローチです。
したがって、わざわざ、イテレータを直接使用する必要はないでしょう。
ただし、一部のアルゴリズムでは直接、イテレータを使用する必要があるかもしれません。
1つの例として、reduce1(_:)
メソッドを挙げます。
reduce1(_:)
メソッドは標準ライブラリのreduce(_:_:)
メソッドと同様に、初期値と結合クロージャを受け取ります。
そして、「シーケンスの最初の要素」を初期値として使用します。
以下は、reduce1(_:)
メソッドの実装です。
シーケンスの残りの部分をループする前に「初期値を取得する」ために、シーケンスのイテレータを直接使用しています。
extension Sequence {
func reduce1(_ nextPartialResult: (Element, Element) -> Element) -> Element?
{
var i = makeIterator()
guard var accumulated = i.next() else {
return nil
}
while let element = i.next() {
accumulated = nextPartialResult(accumulated, element)
}
return accumulated
}
}
Reduce1(_:)
メソッドは、特定の種類のシーケンス操作をより簡単にします。
先ほど紹介したanimals
配列を例として、シーケンス内で最も長い文字列を見つける方法は次のとおりです。
let longestAnimal = animals.reduce1 { current, element in
if current.count > element.count {
return current
} else {
return element
}
}
print(longestAnimal)
// Prints Optional("Butterfly")
Using Multiple Iterators
複数のイテレータを使用するには
単一のシーケンスで複数のイテレータ(またはfor-in
ループ)を使用するときは、その具体的な型を知っているか、シーケンスがcollection
プロトコルにも制限されているため、特定のシーケンスが繰り返し反復をサポートすることを知っていることを確認してください。
Whenever you use multiple iterators (or for-in loops) over a single sequence, be sure you know that the specific sequence supports repeated iteration, either because you know its concrete type or because the sequence is also constrained to the Collection protocol.
コピーするのではなく、シーケンスのmakeIterator()
メソッドをその都度呼び出して、別個のイテレータを取得します。
イテレータをコピーすることは安全ですが、同じイテレータのnext()
メソッドを呼び出してコピーを進めると、そのイテレータの他のコピーが無効になる恐れがあります。
この点でfor-in
ループは安全です。
Adding IteratorProtocol Conformance to Your Type
独自の型をIteratroProtocolに適合させるには
IteratorProtocol
に適合するためのイテレータは、簡単に実装できます。
関連するシーケンスのステップを1つ進めて、現在の要素を返すnext()
メソッドを宣言します。
シーケンスを最後まで進めたら、next()
メソッドはnil
を返します。
例として、独自のCountdown
型シーケンスを考えます。
カウントダウンのシーケンスを開始する整数で初期化したら、ゼロまで反復処理できます。
Countdown
構造体の定義はわずかです。
Sequence
プロトコルに必要な開始カウントとmakeIterator()
メソッドのみが含まれています。
struct Countdown: Sequence {
let start: Int
func makeIterator() -> CountdownIterator {
return CountdownIterator(self)
}
}
makeIterator()
メソッドは、独自に定義したCountdownIterator
型のイテレータを返します。
CountdownIterator
型は、「反復中のカウントダウンシーケンス」と「値を返した回数」の両方を追跡します。
struct CountdownIterator: IteratorProtocol {
let countdown: Countdown // 反復中のシーケンス
var times = 0 // 値を返した回数
// 初期化時に「反復中のシーケンス」を受け取る
init(_ countdown: Countdown) {
self.countdown = countdown
}
// シーケンスを進める
mutating func next() -> Int? {
let nextNumber = countdown.start - times // 開始が10で3回目なら、次は7
// 次の数が0より大きくなければ、nilを返して終了
guard nextNumber > 0 else { return nil }
// 次の数が0より大きいなら「値を返した回数」をインクリメントして、「次の数」を返す
times += 1
return nextNumber
}
}
CountdownIterator
インスタンスでnext()
メソッドが呼び出されるたびに、新しい次の値を計算し、ゼロに達したかどうかを確認し、イテレータが終了した場合はnil
を、そうでなければ「シーケンスの要素」を返します。
カウントダウンのシーケンスを作成して反復処理するには、CountdownIterator
型インスタンスに対して反復処理を行います。
let threeTwoOne = Countdown(start: 3)
for count in threeTwoOne {
print("\(count)...")
}
// Prints "3..."
// Prints "2..."
// Prints "1..."