作者:Ole Begemann,原文鏈接,原文日期:2017/02/06
譯者:Cwift;校對:walkingway;定稿:CMB
Collection 協(xié)議是 Swift 集合類型的根基。除了 Collection,標準庫還提供了另外四個協(xié)議,用來擴展集合類型的功能。這些協(xié)議改進了 Collection —— 遵守任何一個協(xié)議的對象也必須遵守 Collection。

它們分別是:
- BidirectionalCollection:可以向前和向后遍歷的集合。比如 [String.CharacterView] (https://developer.apple.com/reference/swift/string.characterview)。(雖然在 characters 集合上你不能高效地跳轉(zhuǎn)到任意位置(因為字形集群是變長的),但是指定下標依舊可以找到對應的 Character。)
- RandomAccessCollection:可以在常量時間訪問任何元素的集合。Array 就是一個規(guī)范的例子。
- MutableCollection:支持集合通過下標的方式改變自身的元素,即 array[index] = newValue。
- RangeReplaceableCollection:支持插入和刪除任意區(qū)間的元素集合。
RandomAccessCollection 協(xié)議改進了 BidirectionalCollection 協(xié)議,因為前者是后者的嚴格超集 —— 任何可以有效地跳轉(zhuǎn)到任意索引的集合都可以向后遍歷。RandomAccessCollection 沒有基于 BidirectionalCollection 提供新的 API;也就是說前者能做的事情,后者都可以做到。然而,RandomAccessCollection 嚴格的特性保證了遵守者中的算法只能通過隨機元素訪問來實現(xiàn)。
你可能認為 RangeReplaceableCollection 應該類似地改進于 MutableCollection,因為突變可以用插入和刪除來建模,但事實并不是這樣。這兩個協(xié)議是在同一層的 —— 一些類型只符合 MutableCollection(例如 UnsafeMutableBufferPointer),一些只適用于RangeReplaceableCollection(例如String.CharacterView),只有一部分同時遵守了兩者(Array 和 Data)。
稍后將通過一些示例來解釋原因。
Set
MutableCollection
MutableCollection 支持元素在原位置修改。該協(xié)議在 Collection 的基礎(chǔ)上新增的 API 是下標必須提供一個 setter。
Set 是一個 Collection,并且它當然是可變的。所以應該遵守 MutableCollection 協(xié)議,這看起來合情合理,但是事實并非如此。主要是因為協(xié)議的語義。MutableCollection 允許更改集合元素的值,但是協(xié)議的文檔規(guī)定:突變必須既不改變集合的長度,也不能改變元素的順序。Set 不能滿足任何一項。
首先來說保留長度的問題。Set 不能包含重復的元素,因此,如果你使用一個 Set 中已經(jīng)存在的值來替換某個元素,則該 Set 會在變異后減少一個元素。
Set 也是一種無序的集合 —— 使用它的時候元素的順序總是無序的。不過在其內(nèi)部,Set 依據(jù)其構(gòu)成的規(guī)則具有穩(wěn)定的順序。當你通過下標更改 MutableCollection 的遵守者時, 被更改元素的索引必須保持不變,即索引在集合中所指示的位置不能改變。Set 不能滿足這個要求,因為一個 SetIndex 指向的是桶狀(bucket)的內(nèi)部存儲結(jié)構(gòu),當內(nèi)部元素變異時,該存儲桶會發(fā)生變化。
RangeReplaceableCollection
RangeReplaceableCollection 向 Collection 中增加了兩個要求:一個空的構(gòu)造器以便可以創(chuàng)建一個新的空集合,以及 replaceSubrange(_:with:) 方法,該方法用另一個集合替換當前集合指定范圍中的元素。目標范圍和用來替換的集合的長度可以不同,其中任何一個都可以為空。協(xié)議使用這一方法提供在任意位置移除和插入元素的默認實現(xiàn)。
Set 也不是一個 RangeReplaceableCollection。與它不能遵守 MutableCollection 協(xié)議的原因相同:不滿足協(xié)議的語義。
文檔中對 replaceSubrange(_:with:) 方法的描述如下:從集合中刪除指定范圍的元素并在同一位置插入新元素。這與 “Set 在任意位置插入或移除元素都可以改變集合中元素的內(nèi)部位置”的情況不兼容。然而,即便 Set 以某種方式維持內(nèi)部元素順序的穩(wěn)定,RangeReplaceableCollection 協(xié)議的語義也不適合它,因為協(xié)議定義的大多數(shù)方法對 Set 都沒有意義。例如,在 RangeReplaceableCollection 中 append(_:) 方法的意義是在集合的尾部插入一個新元素。Set 中相關(guān)操作的正確稱呼是 insert(_:) ,因為你不能 append 任何元素到一個無序的集合中。
Dictionary
Dictionary 的故事與 Set 基本相同。兩者都是基于哈希表實現(xiàn)的無序集合。(Dictionary 與 Set 是如此相似,它們在標準庫中共享同一個實現(xiàn),使用 GYB 模板語言生成代碼。你可以在 HashedCollections.swift.gyb 中找到相關(guān)資料)因此,由于相同的原因 Dictionary 不能遵守 MutableCollection 和 RangeReplaceableCollection。
另外一個方面是 Dictionary 獨有的。字典的元素類型 —— 即它把 Collection 指定的關(guān)聯(lián)類型 Iterator.Element 特化后的類型 —— 是一個 (Key, Value) 類型的元組:
struct Dictionary<Key: Hashable, Value>: Collection {
typealias Element = (key: Key, value: Value)
typealias Index = DictionaryIndex<Key, Value>
subscript(position: Index) -> Element {
...
}
...
}
這意味著在字典中所有從 Collection 和 Sequence 繼承來的 API 都會將 (Key, Value) 作為字典的元素。所以當你在 for 循環(huán)中遍歷一個 Dictionary 時,得到一個(Key,Value)。
Index 是一個完全獨立的類型,并且與 Key 的類型沒有任何關(guān)聯(lián)。因此,你所熟悉的接受一個 Key 并返回一個 Optional<Value> 的下標并不是 Collection 協(xié)議所提供的下標。相反,它是直接在 Dictionary 上定義的,與 Collection 毫無關(guān)聯(lián)。大多數(shù)情況下你會使用 Dictionary 特定的下標,但是 Collection 中的變體依舊存在:
let dict = ["Berlin": 1237, "New York": 1626]
dict["Berlin"] // returns Optional<Int>
for index in dict.indices {
dict[index] // returns (key: String, value: Int) (not optional!)
}
如果字典從 MutableCollection 和/或RangeReplaceableCollection 中獲取方法,方法需要同時操作 Index 的值以及 (Key, Value) ,即使從實現(xiàn)上來說可以做到兼容二者,但是這種一致性可能不值得投入精力。
結(jié)論
Sequence 和 Collection 組成了 Swift 中集合類型的根基。而專門性的集合類型 BidirectionalCollection、RandomAccessCollection、MutableCollection 和 RandomAccessCollection 對你自定義的類型和算法的功能和性能特性提供了非常細粒度的控制。語義是決定類型是否應該遵守這些協(xié)議中的一個或多個的重要因素。
在下一篇文章中,我將討論一個符合專門性集合協(xié)議上下文的有趣類型: String.CharacterView。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請訪問 http://swift.gg。