使用 Swift 開發 App 時,我們每天會寫數十次像 for item in array 這樣的迴圈。因為太習慣了,直到嘗試用 for-in 迭代自訂型別並遇到編譯錯誤,才會好奇:「這個迴圈到底在內部做了什麼?」
先說結論,Swift 的 for-in 迴圈是由 Sequence 和 IteratorProtocol 這兩個協定共同運作的。Sequence 宣告「我可以被迭代」,IteratorProtocol 則負責實際取出值:「我會提供下一個值。」
本文會整理這兩個協定如何分工、for-in 如何在內部展開,以及如何自行建立自訂序列。若你覺得迭代器模式很難,請讀到最後。
Sequence 和 IteratorProtocol 有什麼不同?
先從最容易混淆的地方開始。兩者都與迭代有關,但職責明確不同。
IteratorProtocol 只有一項要求:mutating func next() -> Element?。每次呼叫會回傳下一個元素;沒有更多元素時則回傳 nil。
也就是說,迭代器是「攜帶狀態的游標」。它記住目前讀到的位置,每次呼叫 next() 就前進一格。
Sequence 位於更高一層,負責透過 makeIterator() 方法建立並交出迭代器。就像是在說:「想迭代我,就使用這個迭代器。」
總結如下。
- IteratorProtocol:逐一取出值的游標。核心是 next()。
- Sequence:建立游標的工廠。核心是 makeIterator()。
陣列、字典、Set、字串、Range……Swift 標準函式庫中可迭代的型別都遵循 Sequence。
for-in 在內部是如何展開的?
這是本文的核心。我們不經意使用的 for-in,其實是語法糖(syntactic sugar)。
以下是我們平常使用的迴圈。
for number in [1, 2, 3] {
print(number)
}
編譯器大致會將這段程式碼轉換成以下形式來處理:用 makeIterator() 建立迭代器,並持續執行 while 迴圈,直到 next() 回傳 nil。
var iterator = [1, 2, 3].makeIterator()
while let number = iterator.next() {
print(number) // 1, 2, 3 依序輸出
}
for-in 不是魔法,歸根究柢只是 makeIterator() 與 next() 的組合。
理解這個結構後,就能自然明白為什麼只遵循 Sequence 的型別可以直接用於 for-in,因為 for-in 只需要 makeIterator()。
如何建立自訂序列?
親自實作一次就會更有概念。以下以能無限產生 Fibonacci 數列的序列為例。
最簡單的方法是在一個小型別中同時遵循 Sequence 和 IteratorProtocol。這樣 makeIterator() 的預設實作就會複製並回傳自身。
struct Fibonacci: Sequence, IteratorProtocol {
var current = 0
var nextValue = 1
mutating func next() -> Int? {
defer { (current, nextValue) = (nextValue, current + nextValue) }
return current
}
}
next() 的工作很單純:在回傳目前值之前,使用 defer 預先計算下一個狀態。
現在這個型別就能直接用於 for-in。因為是不定長度序列,所以必須自行限制迭代次數。
for n in Fibonacci().prefix(8) {
print(n) // 0 1 1 2 3 5 8 13
}
也別忘了可以直接使用 prefix、map、filter 等方法。遵循 Sequence 後,這些標準運算就會透過協定延伸自動提供,不必自行實作。
常見問題 Q&A
Q. 只遵循 Sequence 就可以嗎?IteratorProtocol 也一定需要嗎? A. 要執行 for-in,最終一定要有迭代器。不過如上例,若同一型別同時遵循兩者,就不必另外實作 makeIterator()。若想分離迭代狀態與集合,可將迭代器獨立成另一個 struct。
Q. 將序列用 for-in 迭代一次後,再迭代一次會怎樣? A. 陣列等值型別每次都會建立新的迭代器,因此能安全地迭代多次。但網路串流等消費後即消失的序列只能迭代一次,請特別注意。
Q. 它和 Collection 有什麼不同? A. Collection 是繼承 Sequence 的更高階概念,要求能以索引多次存取、計算 count 並保證順序等更多能力。若只需單向瀏覽值,Sequence 就足夠了。
原來一行 for-in 背後藏著 Sequence 與 IteratorProtocol 的清楚分工:迭代器負責游標,序列負責工廠。分清兩者後,建立自訂迭代型別就容易多了。
下次使用 for-in 時,不妨想一下「現在正在呼叫 next()」。也建議你從親自建立 Fibonacci 序列開始。

