在Swift 4中,對原有的Collection體系做了一些簡化。為此,通過這個系列,我們重新過一遍和Collection相關的內容。在這個系列裡,我們拋開Swift中那些具體的Array,Set,Dictionary不談,單純從表達一系列元素這個概念出發,來回顧Swift Collections設計的前前後後,為什麼需要Iterator?Index和Iterator的關係和區別是什麼?為什麼需要Sequence?Collection和Sequence的差別是什麼?如何為不同的集合型別設計介面?。通過對這些問題的討論,你不僅能夠通這個系列的內容更好的使用集合型別。還能以一個標準庫庫設計者的視角,來理解一些你可能平時不太會注意到的細節。
從迭代一系列元素開始
在Swift 4中,對原有的Collection
體系做了一些簡化。為此,通過這個系列,我們重新過一遍和Collection
相關的內容。
拋開Swift中那些具體的Array
,Set
,Dictionary
不談,單純從為了表達一堆數字這個角度來看,其中最基礎的動作,就是要能逐個訪問到它們。因此,我們關於集合這個抽象概念本身的話題,不妨就從這個動作開始。
這裡我們用“一堆數字”這個形式舉例便於理解,實際上,作為集合,它表達的是任意一堆物件。
從約束一個最基本的遍歷動作開始
為了用最簡單的形式描述“逐個訪問”這個動作,我們可以定義一個Protocol
:
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
複製程式碼
其中,Element
定義了我們逐個訪問到的元素型別,而next
方法則不斷給我們返回下一個元素,所有元素都訪問完了,next
返回nil
。另外,為什麼我們要用mutating
修飾next
呢?大家先不用在意這個事情,在後面的內容裡,就明白了。
用IteratorProtocol表示無窮序列
有了IteratorProtocol
,怎麼用呢?來看個最簡單的例子:
struct Ones: IteratorProtocol {
mutating func next() -> Int? {
return 1
}
}
複製程式碼
這裡,One
表示一個包含無窮多個數字1的序列,為了逐個訪問到這些數字,我們只要不斷呼叫next
就好了:
var ones = Ones()
ones.next() // Optional(1)
ones.next() // Optional(1)
ones.next() // Optional(1)
複製程式碼
雖然例子很簡單,但有兩點還是值得說一下:
- 第一,我們無需顯式指定
Element
的型別,編譯器可以從next()
的簽名中推匯出來; - 第二,這個例子要表達的重點是,我們無需關心
ones
這個序列的內部構成,只要知道呼叫next
就可以得到下一個元素,或者得到nil
,表示結束就好了; 當然,除了這種為了演示而編寫的程式碼之外,我們也可以定義一些有用的Iterator,例如一個Fibonacci序列:
struct Fibonacci: IteratorProtocol {
private var state = (0, 1)
mutating func next() -> Int? {
let nextNumber = state.0
self.state = (state.1, state.0 + state.1)
return nextNumber
}
}
複製程式碼
而用法,和ones
如出一轍:
var fibs = Fibonacci()
fibs.next() // Optional(0)
fibs.next() // Optional(1)
fibs.next() // Optional(1)
fibs.next() // Optional(2)
複製程式碼
看到這,你就明白為什麼我們要讓next
是一個mutating
方法了。為了遍歷序列,我們幾乎總是需要一些用於記錄狀態的屬性,為了讓next
可以修改這些屬性,必須用mutating
來修飾它。
使用IteratorProtocol表達序列的侷限性
但是,使用遵從IteratorProtocol
的型別來表達序列,有一個讓人困惑的地方。當我們要反覆從頭遍歷一個序列的時候,該怎麼辦呢?顯然,複製一個fibs
並不行,它會從當前遍歷的狀態繼續下去。我們唯一能做的,就是重新建立一個Fibonacci
物件。
當然,由於Fibonacci
是我們自己實現的,因此,我們瞭解這前前後後的所有細節。但是,當我們拿著Fibonacci
給別人使用的時候,這種詭異的用法一定不會為你贏得同事的好評。
之所以會有這種問題,是因為我們借用了遍歷的當前狀態作為了序列的本身。ones也好,fibs也好,都如此。每一次呼叫next,我們用於記錄當前遍歷狀態的值,恰好生成了我們期望的整個序列。
What's next?
為了解決這個問題,我們應該削弱遵循IteratorProtocol
的型別表達的語意,讓它在大多時候都只承載遍歷的含義,而對於我們要表達的序列本身,再用一類約束來表達,這就是為什麼Swift要提供protocol Sequence
的原因。下一節,我們就來了解這個protocol
。