Collections in Swift 4

泊學發表於2017-12-28

在Swift 4中,對原有的Collection體系做了一些簡化。為此,通過這個系列,我們重新過一遍和Collection相關的內容。在這個系列裡,我們拋開Swift中那些具體的Array,Set,Dictionary不談,單純從表達一系列元素這個概念出發,來回顧Swift Collections設計的前前後後,為什麼需要Iterator?Index和Iterator的關係和區別是什麼?為什麼需要Sequence?Collection和Sequence的差別是什麼?如何為不同的集合型別設計介面?。通過對這些問題的討論,你不僅能夠通這個系列的內容更好的使用集合型別。還能以一個標準庫庫設計者的視角,來理解一些你可能平時不太會注意到的細節。


從迭代一系列元素開始

泊學4K視訊 泊閱文件

在Swift 4中,對原有的Collection體系做了一些簡化。為此,通過這個系列,我們重新過一遍和Collection相關的內容。

拋開Swift中那些具體的ArraySetDictionary不談,單純從為了表達一堆數字這個角度來看,其中最基礎的動作,就是要能逐個訪問到它們。因此,我們關於集合這個抽象概念本身的話題,不妨就從這個動作開始。

這裡我們用“一堆數字”這個形式舉例便於理解,實際上,作為集合,它表達的是任意一堆物件。

從約束一個最基本的遍歷動作開始

為了用最簡單的形式描述“逐個訪問”這個動作,我們可以定義一個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

相關文章