老司机带你深入浅出 Collection
作者:Ole Begemann,原文鏈接,原文日期:2016-09-22
譯者:BigbigChai;校對(duì):walkingway;定稿:CMB
本文摘自即將出新版的 Swift 進(jìn)階(Advanced Swift)一書(shū)中的集合協(xié)議(Collection Protocols)章節(jié)(稍作修改以適合博客文章)。我和 Chris Eidhof 已經(jīng)基本完成為本書(shū)更新到 Swift 3 的工作,很快可以面世。
Swift 中的集合非常強(qiáng)大,但也很復(fù)雜。如果你想實(shí)現(xiàn)自定義的集合類(lèi)型,首先需要了解集合協(xié)議的原理。即使只是使用標(biāo)準(zhǔn)庫(kù)中常見(jiàn)的集合類(lèi)型,它的工作原理仍然十分值得學(xué)習(xí),尤其是它可以幫助你理解編譯器打印出來(lái)的錯(cuò)誤信息。
在本文中,我們想探討一下集合協(xié)議的關(guān)聯(lián)類(lèi)型。這聽(tīng)起來(lái)像是一個(gè)晦澀的主題,但我認(rèn)為想要掌握 Swift 中集合類(lèi)型的關(guān)鍵:在于對(duì)理解關(guān)聯(lián)類(lèi)型的作用、以及為什么需要它們。
概述
集合有五種關(guān)聯(lián)類(lèi)型。它們聲明如下(實(shí)際的代碼并不是這樣,因?yàn)?Index 是在 IndexableBase 中聲明的,但你明白我的意思就好):
protocol Collection: Indexable, Sequence {associatedtype Iterator: IteratorProtocol = IndexingIterator<Self>associatedtype SubSequence: IndexableBase, Sequence = Slice<Self>associatedtype Index: Comparable // declared in IndexableBaseassociatedtype IndexDistance: SignedInteger = Intassociatedtype Indices: IndexableBase, Sequence = DefaultIndices<Self>... }前四個(gè)關(guān)聯(lián)類(lèi)型繼承自基礎(chǔ)協(xié)議
Sequence,Indexable 和 IndexableBase 1;集合遵循了以上所有的協(xié)議,只是 Index 的約束更加嚴(yán)格、余下的協(xié)議賦予了不同的默認(rèn)值。
注意,除了 Index 以外,集合類(lèi)型的關(guān)聯(lián)類(lèi)型都有默認(rèn)值 — 因此遵守集合協(xié)議的類(lèi)型都只需指定 Index 的類(lèi)型就可以了。雖然你不必過(guò)分在意其他的關(guān)聯(lián)類(lèi)型,但還是應(yīng)該大致了解一下。
迭代器 Iterator
遵守 Sequence 協(xié)議。Sequence 通過(guò)創(chuàng)建迭代器來(lái)訪問(wèn)它們的元素。迭代器每次產(chǎn)生一個(gè)序列的值,并在遍歷該序列時(shí)追蹤它的迭代狀態(tài)。
迭代器內(nèi)部有一個(gè)稱為 Element 的關(guān)聯(lián)類(lèi)型。Element 類(lèi)型指定了迭代器的生成值類(lèi)型。例如,對(duì)于 String.CharacterView 的迭代器而言,Element 的類(lèi)型是 Character。另外,迭代器也定義了它的 Sequence 的 Element 類(lèi)型;事實(shí)上,我們經(jīng)常能在方法簽名、或者 Sequence 和集合的泛型約束中看到對(duì) Iterator.Element 的引用,就是因?yàn)?Element 是 IteratorProtocol 的關(guān)聯(lián)類(lèi)型。
集合的默認(rèn)迭代器類(lèi)型是 IndexingIterator <Self>。這是一個(gè)非常簡(jiǎn)單的封裝結(jié)構(gòu)體,它使用集合自身的索引來(lái)遍歷每個(gè)元素。標(biāo)準(zhǔn)庫(kù)中的大多數(shù)集合都使用 IndexingIterator 作為迭代器。我們不需要為自定義的集合更改迭代器類(lèi)型。
子序列 SubSequence
子序列也遵守 Sequence 協(xié)議,但是集合約束更加嚴(yán)格:集合的子序列本身也應(yīng)該是集合。(我們說(shuō)“應(yīng)該”而不是“必須”,因?yàn)檫@種約束在目前的類(lèi)型系統(tǒng)中無(wú)法完全表示。)
在返回初始集合片段的操作中,子序列作為其返回類(lèi)型:
prefix 和 suffix — 取開(kāi)頭或末尾的 n 個(gè)元素。
dropFirst 和 dropLast — 返回刪除開(kāi)頭或末尾 n 個(gè)元素后的子序列。
拆分(split) — 以指定的分隔符元素拆分序列,并以數(shù)組形式返回。
帶有 Range <Index> 參數(shù)的 subscript(下標(biāo))— 返回指定索引范圍內(nèi)的元素片段。
集合的默認(rèn)子序列類(lèi)型是 Slice <Self>,它封裝了初始的集合(類(lèi)似于 IndexingIterator ),并存儲(chǔ)該片段在初始集合中的起始索引(startIndex)和結(jié)束索引(endIndex)。
自定義集合的子序列類(lèi)型非常有用,特別是當(dāng)它定義為 Self(即集合的片段與集合本身類(lèi)型相同)的時(shí)候。標(biāo)準(zhǔn)庫(kù)類(lèi)型中的例子有 String.CharacterView,這讓字符串片段的使用更為方便。而一個(gè)反例是 Array,它以 ArraySlice 作為片段類(lèi)型。
索引 Index
索引表示集合中的位置。每個(gè)集合都有兩個(gè)特殊的索引,startIndex 和 endIndex。 startIndex 指向集合的第一個(gè)元素,而 endIndex 是集合中最后一個(gè)元素之后的索引。索引應(yīng)該是一個(gè)啞值,只存儲(chǔ)表明元素位置所需的最少信息量。尤其,索引應(yīng)該盡可能地減少對(duì)集合的引用。集合索引必須是可比較的,這是它唯一的要求。也就是說(shuō),索引需要有明確的順序。
比如數(shù)組就是用整數(shù)作為索引的,但是整數(shù)索引不是對(duì)所有數(shù)據(jù)結(jié)構(gòu)都起作用。我們?cè)僖?String.CharacterView 為例,Swift 中的字符是大小可變的;如果你想使用整數(shù)索引,你有兩個(gè)選擇:
用索引表示字符串內(nèi)部存儲(chǔ)的偏移量。這種做法十分有效率;訪問(wèn)一個(gè)給定索引的元素的復(fù)雜度是 O(1)。但是對(duì)于索引范圍而言會(huì)有差別。例如,如果索引 0 處的字符是正常大小的兩倍,則下一個(gè)字符的索引會(huì)是 2 - 訪問(wèn)索引 1 處的元素將觸發(fā)致命錯(cuò)誤或未定義行為。這會(huì)嚴(yán)重違反用戶的期望。
用索引 n 表示字符串中的第 n 個(gè)字符。這與用戶期望一致 — 對(duì)索引范圍來(lái)說(shuō)不會(huì)有任何差別。然而,訪問(wèn)給定索引的元素的復(fù)雜度變成了O(n);必須從頭遍歷字符串內(nèi)該索引之前的所有元素,才能確定字符的儲(chǔ)存位置。這種行為非常不好,因?yàn)橛脩魰?huì)期望通過(guò)索引下標(biāo)訪問(wèn)元素的操作能瞬間完成。
?
因此,String.CharacterView.Index 是一個(gè)不可見(jiàn)的值,指向字符串的內(nèi)部存儲(chǔ)緩沖區(qū)中的位置。實(shí)際上,它只是封裝了一個(gè)整型偏移量,集合的使用者并不會(huì)對(duì)這種實(shí)現(xiàn)細(xì)節(jié)感興趣。
每個(gè)集合都需要分別選擇正確的索引類(lèi)型。因此,關(guān)聯(lián)類(lèi)型中索引是唯一沒(méi)有默認(rèn)值的。
索引距離 IndexDistance
索引距離是一個(gè)帶符號(hào)的整型,表示兩個(gè)索引之間的距離。默認(rèn)值是整型,我們沒(méi)必要自己修改。
索引范圍 Indices
這是集合的 indices 屬性的返回類(lèi)型。它是一個(gè)包含所有索引的集合,該集合中的索引以升序排列對(duì)應(yīng)初始集合的下標(biāo)。注意,endIndex 不包括在內(nèi),因?yàn)?endIndex 表示”結(jié)束之后”的位置,所以不是有效的下標(biāo)參數(shù)。
在 Swift 2 中,indices 屬性返回一個(gè) Range <Index>,可以用來(lái)遍歷集合中所有的有效索引。在 Swift 3 中,Range <Index> 不再可迭代,因?yàn)樗饕荒茏晕疫f進(jìn)(現(xiàn)在由集合來(lái)推進(jìn)索引迭代)。Indices 類(lèi)型替代了 Range <Index> 來(lái)實(shí)現(xiàn)索引的迭代。
默認(rèn)的 Indices 類(lèi)型十分具有想象力地命名為 DefaultIndices <Self>(23333)。它跟 Slice 一樣,是對(duì)初始集合、起始和結(jié)束索引的一個(gè)簡(jiǎn)單封裝 — 它需要保留對(duì)初始集合的引用,以便能夠推進(jìn)索引。如果在集合迭代索引的過(guò)程中對(duì)集合進(jìn)行修改,可能會(huì)導(dǎo)致意想不到的性能問(wèn)題:假設(shè)集合的實(shí)現(xiàn)使用了寫(xiě)時(shí)復(fù)制(copy-on-write)(正如標(biāo)準(zhǔn)庫(kù)中的所有集合類(lèi)型),迭代開(kāi)始之后對(duì)集合的額外引用可能觸發(fā)不必要的復(fù)制。
我們?cè)跁?shū)中廣泛說(shuō)明了寫(xiě)時(shí)復(fù)制的內(nèi)容。就現(xiàn)在來(lái)說(shuō),知道自定義集合可以使用一個(gè)不引用初始集合的 Indices 類(lèi)型就足夠了,這樣做是一個(gè)非常有益的優(yōu)化。所有索引不依賴于集合本身的集合都可以這樣使用,例如數(shù)組。如果數(shù)組的索引是一個(gè)整數(shù)類(lèi)型,你可以使用 CountableRange <Index>。以下是對(duì)自定義隊(duì)列類(lèi)型的定義(我們?cè)跁?shū)中實(shí)現(xiàn)了此類(lèi)型):
extension Queue: Collection {...typealias Indices = CountableRange<Int>var indices: CountableRange<Int> {return startIndex..<endIndex} }本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請(qǐng)?jiān)L問(wèn) http://swift.gg。
考慮這是更好地支持泛型的部分,希望明年當(dāng)這個(gè)讓人高度期待的特性出來(lái)時(shí),Indexable 和 IndexableBase 會(huì)被去掉,而把它們的功能放在集合內(nèi)部。 ?
總結(jié)
以上是生活随笔為你收集整理的老司机带你深入浅出 Collection的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android Studio 使用Lam
- 下一篇: 读取pandas修改单列数据类型