版本記錄
版本號 | 時(shí)間 |
---|---|
V1.0 | 2020.06.27 星期六 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面少梁,用戶交互也是通過UIKit進(jìn)行的。感興趣的參考上面幾篇文章凳鬓。
1. UIKit框架(一) —— UIKit動力學(xué)和移動效果(一)
2. UIKit框架(二) —— UIKit動力學(xué)和移動效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用魁亦、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用虚吟、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動畫的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動畫的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個(gè)UISplitViewController的簡單實(shí)用示例 (一)
29. UIKit框架(二十九) —— 一個(gè)UISplitViewController的簡單實(shí)用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(二)
32. UIKit框架(三十二) —— 替換Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替換Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView轉(zhuǎn)盤效果的實(shí)現(xiàn)(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
開始
首先看下主要內(nèi)容:
本文主要講述了如何使用
collection protocol
來創(chuàng)建自己的Bag collection
類型的實(shí)現(xiàn)。內(nèi)容來自翻譯怠肋。
下面是寫作環(huán)境:
Swift 5, iOS 13, Xcode 11
下面就是正文了
Array
敬鬓,Dictionary
和Set
是Swift標(biāo)準(zhǔn)庫中捆綁在一起的常用集合類型。 但是,如果他們沒有立即提供您的應(yīng)用所需的一切钉答,該怎么辦础芍? 別擔(dān)心。 您可以使用Swift標(biāo)準(zhǔn)庫中的協(xié)議創(chuàng)建自己的自定義集合数尿!
Swift中的集合(Collections)
帶有大量方便的實(shí)用程序仑性,可用于對它們進(jìn)行迭代,過濾和更多操作右蹦。 除了使用自定義集合之外诊杆,您還可以將所有業(yè)務(wù)邏輯添加到自己的代碼中。 但是何陆,這會使您的代碼腫晨汹,難以維護(hù)并且無法復(fù)制標(biāo)準(zhǔn)庫提供的內(nèi)容。
幸運(yùn)的是贷盲,Swift
提供了強(qiáng)大的收集協(xié)議(collection protocol)
淘这,因此您可以創(chuàng)建自己的收集類型,這些收集類型專門為滿足應(yīng)用程序的需求而量身定制巩剖。 您只需實(shí)現(xiàn)這些協(xié)議即可利用Swift集合的強(qiáng)大功能铝穷。
在本教程中,您將從頭開始構(gòu)建一個(gè)多集multiset(也稱為bag)球及。
在此過程中氧骤,您將學(xué)習(xí)如何:
- 采用以下協(xié)議:
Hashable呻疹,Sequence吃引,Collection,CustomStringConvertible刽锤,ExpressibleByArrayLiteral和ExpressibleByDictionaryLiteral
镊尺。 - 為您的集合創(chuàng)建自定義初始化。
- 使用自定義方法改進(jìn)自定義集合并思。
是時(shí)候開始了庐氮!
注意:本教程適用于
Swift 5.0
。 由于對Swift標(biāo)準(zhǔn)庫進(jìn)行了重大更改宋彼,因此無法編譯以前的版本弄砍。
在起始文件夾中打開文件Bag.playground
。
注意:如果愿意输涕,可以創(chuàng)建自己的
Xcode playground
音婶。 如果這樣做,請刪除所有默認(rèn)代碼以從一個(gè)空的playground
開始莱坎。
Creating the Bag Struct
接下來衣式,將以下代碼添加到您的playground
:
struct Bag<Element: Hashable> {
}
就這樣, “Papa’s got a brand new bag”!
您的Bag
是一種通用結(jié)構(gòu)碴卧,需要一個(gè)Hashable
元素類型弱卡。 要求使用Hashable
元素可以比較和存儲O(1)
時(shí)間復(fù)雜度的唯一值。 這意味著住册,無論其內(nèi)容物的大小如何婶博,Bag
都將以恒定的速度運(yùn)行。 另外荧飞,請注意凡蜻,您正在使用struct
; 就像Swift
對標(biāo)準(zhǔn)集合所做的那樣,這會強(qiáng)制執(zhí)行值語義悍募。
Bag
就像Set
溪厘,因?yàn)樗淮鎯χ貜?fù)的值。 所不同的是:Bag
保留所有重復(fù)值的連續(xù)計(jì)數(shù)忠荞,而Set
則不保留。
像購物清單一樣考慮它帅掘。 如果您想要多個(gè)委煤,則不要多次列出。 您只需在項(xiàng)目旁邊寫上您想要的號碼修档。
要對此建模碧绞,請將以下屬性添加到playground
上的Bag
中:
// 1
fileprivate var contents: [Element: Int] = [:]
// 2
var uniqueCount: Int {
return contents.count
}
// 3
var totalCount: Int {
return contents.values.reduce(0) { $0 + $1 }
}
這些是Bag
所需的基本屬性。 這是每一步的工作:
- 1) contents:使用
Dictionary
作為內(nèi)部數(shù)據(jù)結(jié)構(gòu)吱窝。 這對于Bag
來說非常有用讥邻,因?yàn)樗鼤?qiáng)制執(zhí)行用于存儲元素的唯一鍵。 每個(gè)元素的值就是其計(jì)數(shù)院峡。 請注意兴使,您將此屬性標(biāo)記為fileprivate
,以使Bag
的內(nèi)部工作對外界隱藏照激。 - 2) uniqueCount:返回唯一商品的數(shù)量发魄,忽略其單獨(dú)數(shù)量。 例如俩垃,一個(gè)包含
4
個(gè)橙子和3
個(gè)蘋果的Bag
將返回的uniqueCount
為2
励幼。 - 3) totalCount:返回
Bag
中的物品總數(shù)。 在上面的示例中口柳,totalCount
將返回7
苹粟。
Adding Edit Methods
現(xiàn)在,您將實(shí)現(xiàn)一些方法來編輯Bag
的內(nèi)容啄清。
1. Adding Add Method
在剛添加的屬性下添加以下方法:
// 1
mutating func add(_ member: Element, occurrences: Int = 1) {
// 2
precondition(occurrences > 0,
"Can only add a positive number of occurrences")
// 3
if let currentCount = contents[member] {
contents[member] = currentCount + occurrences
} else {
contents[member] = occurrences
}
}
這是這樣做的:
- 1)
add(_:occurrences :)
:提供一種向Bag
添加元素的方法六水。它帶有兩個(gè)參數(shù):通用類型Element
和一個(gè)可選的出現(xiàn)次數(shù)俺孙。您將方法標(biāo)記為mutating
,因此可以修改contents
實(shí)例變量掷贾。 - 2)
precondition(_:_ :)
:要求大于0
次出現(xiàn)睛榄。如果此條件為假,則執(zhí)行停止想帅,并且遵循該條件的String
將出現(xiàn)在playground
調(diào)試器中场靴。 - 3) 本部分檢查
bag
中是否已存在該元素。如果是這樣港准,它將增加計(jì)數(shù)旨剥。如果沒有,它將創(chuàng)建一個(gè)新元素浅缸。
注意:在本教程中轨帜,您將使用
precondition
,以確保按預(yù)期方式使用Bag
衩椒。您還將使用precondition
進(jìn)行健全性檢查蚌父,以確保在添加功能時(shí)一切正常。逐步執(zhí)行此操作將使您避免意外破壞以前運(yùn)行的功能毛萌。
現(xiàn)在您已經(jīng)可以將元素添加到Bag
實(shí)例中苟弛,還需要一種將其刪除的方法。
2. Implementing the Remove Method
在add(_:occurrences :)
下面添加以下方法:
mutating func remove(_ member: Element, occurrences: Int = 1) {
// 1
guard
let currentCount = contents[member],
currentCount >= occurrences
else {
return
}
// 2
precondition(occurrences > 0,
"Can only remove a positive number of occurrences")
// 3
if currentCount > occurrences {
contents[member] = currentCount - occurrences
} else {
contents.removeValue(forKey: member)
}
}
請注意阁将,remove(_:occurrences :)
與add(_:occurrences :)
具有相同的參數(shù)膏秫。 運(yùn)作方式如下:
- 1) 首先,它檢查該元素是否存在做盅,并且至少具有調(diào)用者要?jiǎng)h除的出現(xiàn)次數(shù)缤削。 如果不是,則該方法返回言蛇。
- 2) 接下來僻他,確保要?jiǎng)h除的出現(xiàn)次數(shù)大于0宵距。
- 3) 最后腊尚,它檢查元素的當(dāng)前計(jì)數(shù)是否大于要?jiǎng)h除的出現(xiàn)次數(shù)。 如果更大满哪,則通過從當(dāng)前計(jì)數(shù)中減去要?jiǎng)h除的出現(xiàn)次數(shù)來設(shè)置元素的新計(jì)數(shù)婿斥。 如果不大,則
currentCount
和occurrences
相等哨鸭,它將完全刪除該元素民宿。
目前Bag
并沒有做太多事情。 您無法訪問其內(nèi)容像鸡,也無法使用任何有用的收集方法(如map活鹰,filter
等)對您的收藏進(jìn)行操作。
但是,一切都不會丟失志群! Swift
提供了使Bag
成為合法集合所需的工具着绷。 您只需要遵循一些協(xié)議即可。
Adopting Protocols
在Swift
中锌云,協(xié)議定義了一組屬性和方法荠医,這些屬性和方法必須在采用它的對象中實(shí)現(xiàn)。 要采用協(xié)議桑涎,只需在class
或struct
的定義后添加一個(gè)冒號彬向,后跟您要采用的協(xié)議名稱即可。 聲明采用協(xié)議后攻冷,請?jiān)趯ο笊蠈?shí)現(xiàn)所需的變量和方法娃胆。 完成后,您的對象將符合協(xié)議等曼。
這是一個(gè)簡單的例子缕棵。 當(dāng)前,Bag
對象在Playground
的結(jié)果側(cè)欄中幾乎沒有顯示任何信息涉兽。
將以下代碼添加到playground
的末尾(結(jié)構(gòu)體外部)以查看Bag
的運(yùn)行情況:
var shoppingCart = Bag<String>()
shoppingCart.add("Banana")
shoppingCart.add("Orange", occurrences: 2)
shoppingCart.add("Banana")
shoppingCart.remove("Orange")
然后按Command-Shift-Enter
執(zhí)行playground
招驴。
這將創(chuàng)建一個(gè)帶有少量水果的Bag
。 如果您查看playground
調(diào)試器枷畏,則會看到對象類型别厘,但不包含任何內(nèi)容。
Adopting CustomStringConvertible
幸運(yùn)的是拥诡,Swift
僅針對這種情況提供了CustomStringConvertible
協(xié)議触趴! 在Bag
的大括號后添加以下內(nèi)容:
extension Bag: CustomStringConvertible {
var description: String {
return String(describing: contents)
}
}
符合CustomStringConvertible
要求實(shí)現(xiàn)一個(gè)名為description
的單個(gè)屬性。 此屬性返回特定實(shí)例的文本表示形式渴肉。
您將在這里放置創(chuàng)建代表數(shù)據(jù)的字符串所需的任何邏輯冗懦。 由于Dictionary
符合CustomStringConvertible
,因此您只需將description
調(diào)用委托給contents
仇祭。
按Command-Shift-Enter
再次運(yùn)行playground
披蕉。
看一下shoppingCart
的最新改進(jìn)的調(diào)試信息:
太棒了! 現(xiàn)在乌奇,在向Bag
添加功能時(shí)没讲,您將可以驗(yàn)證其內(nèi)容。
很好礁苗! 您正在創(chuàng)建自己喜歡的強(qiáng)大集合類型的過程中爬凑。 接下來是初始化。
Creating Initializers
非常煩人的是试伙,您一次只能添加一個(gè)元素嘁信。 您應(yīng)該能夠通過傳遞要添加的對象集合來初始化Bag
于样。
將以下代碼添加到playground
的末尾(但請注意,這尚不能編譯):
let dataArray = ["Banana", "Orange", "Banana"]
let dataDictionary = ["Banana": 2, "Orange": 1]
let dataSet: Set = ["Banana", "Orange", "Banana"]
var arrayBag = Bag(dataArray)
precondition(arrayBag.contents == dataDictionary,
"Expected arrayBag contents to match \(dataDictionary)")
var dictionaryBag = Bag(dataDictionary)
precondition(dictionaryBag.contents == dataDictionary,
"Expected dictionaryBag contents to match \(dataDictionary)")
var setBag = Bag(dataSet)
precondition(setBag.contents == ["Banana": 1, "Orange": 1],
"Expected setBag contents to match \(["Banana": 1, "Orange": 1])")
這就是您期望創(chuàng)建Bag
的方式潘靖。 但是它不會編譯百宇,因?yàn)槟形炊x一個(gè)初始化器來接收其他集合。 您將使用泛型(generics)
秘豹,而不是為每種類型顯式創(chuàng)建初始化方法携御。
在Bag
實(shí)現(xiàn)中的totalCount
下方添加以下方法:
// 1
init() { }
// 2
init<S: Sequence>(_ sequence: S) where
S.Iterator.Element == Element {
for element in sequence {
add(element)
}
}
// 3
init<S: Sequence>(_ sequence: S) where
S.Iterator.Element == (key: Element, value: Int) {
for (element, count) in sequence {
add(element, occurrences: count)
}
}
這是您剛剛添加的內(nèi)容:
- 1) 首先,您創(chuàng)建了一個(gè)空的初始化程序既绕。在定義其他
init
方法時(shí)啄刹,您需要添加此代碼。 - 2) 接下來凄贩,添加了一個(gè)初始化程序誓军,該初始化程序接受符合
Sequence
協(xié)議的所有內(nèi)容,其中該序列的元素與Bag
的元素相同疲扎。這涵蓋了數(shù)組Array
和集合Set
類型昵时。您遍歷序列傳遞的內(nèi)容,并一次添加一個(gè)元素椒丧。 - 3) 此后壹甥,您添加了一個(gè)類似的初始化程序,但是它接受類型為
(Element壶熏,Int)
的元組句柠。字典就是一個(gè)例子。在這里棒假,您遍歷序列中的每個(gè)元素并添加指定的計(jì)數(shù)溯职。
再次按Command-Shift-Enter
即可運(yùn)行playground
。請注意帽哑,您之前添加在底部的代碼現(xiàn)在可以使用谜酒。
1. Initializing Collections
這些通用的初始化程序?yàn)?code>Bag對象啟用了更多種類的數(shù)據(jù)源。但是妻枕,它們確實(shí)需要您首先創(chuàng)建傳遞給初始化程序的集合僻族。
為了避免這種情況,Swift
提供了兩種協(xié)議來啟用序列文字的初始化佳头。文字(Literals)
為您提供了一種無需顯式創(chuàng)建對象即可寫數(shù)據(jù)的簡便方法鹰贵。
要看到這一點(diǎn),首先將以下代碼添加到您的playground
的末尾:(注意:在添加所需的協(xié)議之前康嘉,這也會產(chǎn)生錯(cuò)誤。)
var arrayLiteralBag: Bag = ["Banana", "Orange", "Banana"]
precondition(arrayLiteralBag.contents == dataDictionary,
"Expected arrayLiteralBag contents to match \(dataDictionary)")
var dictionaryLiteralBag: Bag = ["Banana": 2, "Orange": 1]
precondition(dictionaryLiteralBag.contents == dataDictionary,
"Expected dictionaryLiteralBag contents to match \(dataDictionary)")
上面的代碼是使用Array
和Dictionary
文字而不是對象進(jìn)行初始化的示例籽前。
現(xiàn)在亭珍,要使它們起作用敷钾,請?jiān)?code>CustomStringConvertible擴(kuò)展下面添加以下兩個(gè)擴(kuò)展:
// 1
extension Bag: ExpressibleByArrayLiteral {
init(arrayLiteral elements: Element...) {
self.init(elements)
}
}
// 2
extension Bag: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (Element, Int)...) {
self.init(elements.map { (key: $0.0, value: $0.1) })
}
}
- 1)
ExpressibleByArrayLiteral
用于根據(jù)數(shù)組樣式文字創(chuàng)建Bag
。 在這里肄梨,您可以使用之前創(chuàng)建的初始化程序阻荒,并傳入elements
集合。 - 2)
ExpressibleByDictionaryLiteral
的功能相同众羡,但對于字典樣式的文字而言侨赡。 該映射將元素轉(zhuǎn)換為初始化程序期望的命名元組。
Bag
看起來更像是原生collection
類型粱侣,是時(shí)候嘗試真正的魔術(shù)了羊壹。
Understanding Custom Collections
您現(xiàn)在已經(jīng)學(xué)到了足夠的知識,可以理解什么是自定義集合(collection)
:您定義的集合對象既符合Sequence
協(xié)議又符合Collection
協(xié)議齐婴。
在上一節(jié)中油猫,您定義了一個(gè)初始化程序,該初始化程序接受符合Sequence
協(xié)議的集合對象柠偶。 Sequence
表示一種類型情妖,該類型提供對其元素的順序,迭代訪問诱担。 您可以將序列視為一系列項(xiàng)目毡证,讓您一次遍歷每個(gè)元素。
迭代是一個(gè)簡單的概念蔫仙,但是此功能為您的對象提供了巨大的功能情竹。它允許您執(zhí)行各種強(qiáng)大的操作,例如:
- map(_ :):使用提供的閉包轉(zhuǎn)換序列中的每個(gè)元素后匀哄,返回結(jié)果數(shù)組秦效。
- filter(_ :):返回滿足提供的閉包謂詞的元素?cái)?shù)組。
- sorted(by :):返回基于提供的閉包謂詞排序的元素?cái)?shù)組涎嚼。
要查看Sequence
中可用的所有方法阱州,請查看Apple’s documentation on the Sequence Protocol。
1. Enforcing Non-destructive Iteration
一個(gè)警告:Sequence
不需要符合性的類型是非破壞性的法梯。這意味著迭代后苔货,無法保證以后的迭代會從頭開始。如果您計(jì)劃多次迭代數(shù)據(jù)立哑,那將是一個(gè)巨大的問題夜惭。
要實(shí)施非破壞性迭代,您的對象需要符合Collection
協(xié)議铛绰。
Collection
繼承自Indexable
和Sequence
主要區(qū)別在于诈茧,集合是可以多次遍歷并按索引訪問的序列。
遵循Collection
捂掰,您將免費(fèi)獲得許多方法和屬性敢会。 一些例子是:
- isEmpty:返回一個(gè)布爾值曾沈,指示集合是否為空。
- first:返回集合中的第一個(gè)元素鸥昏。
- count:返回集合中元素的數(shù)量塞俱。
根據(jù)集合中元素的類型,還有更多可用的方法吏垮。 在Apple的Apple’s documentation on the Collection Protocol中查看它們障涯。
抓住你的Bag
,并采用這些協(xié)議膳汪!
Adopting the Sequence Protocol
對集合類型執(zhí)行的最常見操作是遍歷其元素唯蝶。 例如,將以下內(nèi)容添加到playground
的末尾:
for element in shoppingCart {
print(element)
}
與Array
和Dictionary
一樣旅敷,您應(yīng)該能夠遍歷Bag
生棍。 由于當(dāng)前的Bag
類型不符合Sequence
,因此無法編譯媳谁。
現(xiàn)在修復(fù)該問題涂滴。
1. Conforming to Sequence
在ExpressibleByDictionaryLiteral
擴(kuò)展之后添加以下內(nèi)容:
extension Bag: Sequence {
// 1
typealias Iterator = DictionaryIterator<Element, Int>
// 2
func makeIterator() -> Iterator {
// 3
return contents.makeIterator()
}
}
并不需要太多符合Sequence
。 在上面的代碼中晴音,您:
- 1) 創(chuàng)建名為
Iterator
的Typealias
作為DictionaryIterator
柔纵。Sequence
要求知道如何迭代序列。DictionaryIterator
是Dictionary
對象用來迭代其元素的類型锤躁。 您之所以使用這種類型搁料,是因?yàn)?code>Bag將其基礎(chǔ)數(shù)據(jù)存儲在Dictionary中。 - 2) 將
makeIterator()
定義為返回Iterator
的方法系羞,以逐步瀏覽序列中的每個(gè)元素郭计。 - 3) 通過委派
contents
上的makeIterator()
來返回迭代器,該內(nèi)容本身符合Sequence
椒振。
這就是使Bag
符合Sequence
所需的全部昭伸!
您現(xiàn)在可以遍歷Bag
的每個(gè)元素,并獲取每個(gè)對象的計(jì)數(shù)澎迎。 在上一個(gè)for-in
循環(huán)之后庐杨,將以下內(nèi)容添加到playground
的末尾:
for (element, count) in shoppingCart {
print("Element: \(element), Count: \(count)")
}
按Command-Shift-Enter
運(yùn)行playground
。 打開playground
控制臺夹供,您將按順序看到元素的打印輸出及其數(shù)量灵份。
2. Viewing Benefits of Sequence
能夠遍歷一個(gè)Bag
可以啟用Sequence
實(shí)現(xiàn)的許多有用方法。 將以下內(nèi)容添加到playground
的末端哮洽,以查看其中的一些操作:
// Find all elements with a count greater than 1
let moreThanOne = shoppingCart.filter { $0.1 > 1 }
moreThanOne
precondition(
moreThanOne.first!.key == "Banana" && moreThanOne.first!.value == 2,
"Expected moreThanOne contents to be [(\"Banana\", 2)]")
// Get an array of all elements without their counts
let itemList = shoppingCart.map { $0.0 }
itemList
precondition(
itemList == ["Orange", "Banana"] ||
itemList == ["Banana", "Orange"],
"Expected itemList contents to be [\"Orange\", \"Banana\"] or [\"Banana\", \"Orange\"]")
// Get the total number of items in the bag
let numberOfItems = shoppingCart.reduce(0) { $0 + $1.1 }
numberOfItems
precondition(numberOfItems == 3,
"Expected numberOfItems contents to be 3")
// Get a sorted array of elements by their count in descending order
let sorted = shoppingCart.sorted { $0.0 < $1.0 }
sorted
precondition(
sorted.first!.key == "Banana" && moreThanOne.first!.value == 2,
"Expected sorted contents to be [(\"Banana\", 2), (\"Orange\", 1)]")
按Command-Shift-Enter
鍵可以運(yùn)行playground
填渠,并查看它們的運(yùn)行情況。
這些都是使用序列的有用方法 - 您實(shí)際上是免費(fèi)獲得的!
現(xiàn)在揭蜒,您可能對Bag
的使用方式感到滿意横浑,但是這樣做的樂趣在哪里剔桨? 您絕對可以改善當(dāng)前的Sequence
實(shí)現(xiàn)屉更。
Improving Sequence
當(dāng)前,您依靠Dictionary
為您處理繁重的工作洒缀。 很好瑰谜,因?yàn)樗箘?chuàng)建自己的強(qiáng)大集合變得容易。 問題在于它為Bag
用戶帶來了奇怪而令人困惑的情況树绩。 例如萨脑,Bag
返回類型為DictionaryIterator
的迭代器并不直觀。
但是饺饭,Swift
再次來了渤早! Swift
提供類型AnyIterator
來隱藏底層的迭代器。
用以下內(nèi)容替換Sequence
擴(kuò)展的實(shí)現(xiàn):
extension Bag: Sequence {
// 1
typealias Iterator = AnyIterator<(element: Element, count: Int)>
func makeIterator() -> Iterator {
// 2
var iterator = contents.makeIterator()
// 3
return AnyIterator {
return iterator.next()
}
}
}
在此修訂的Sequence
擴(kuò)展中瘫俊,您:
- 1) 將
Iterator
定義為符合AnyIterator
鹊杖,而不是DictionaryIterator
。 然后扛芽,像以前一樣骂蓖,創(chuàng)建makeIterator()
返回一個(gè)Iterator
。 - 2) 通過在
contents
上調(diào)用makeIterator()
創(chuàng)建Iterator
川尖。 下一步需要此變量登下。 - 3) 將
Iterator
包裝在新的AnyIterator
對象中,以轉(zhuǎn)發(fā)其next()
方法叮喳。next()
方法是在迭代器上調(diào)用的方法被芳,用于獲取序列中的下一個(gè)對象。
按Command-Shift-Enter
運(yùn)行playground
馍悟。 您會注意到幾個(gè)錯(cuò)誤:
以前畔濒,您在使用DictionaryIterator
時(shí)使用了key
和value
的元組名稱。 您已經(jīng)從外界隱藏了DictionaryIterator
赋朦,并將暴露的元組名稱重命名為element
和count
篓冲。
要修復(fù)錯(cuò)誤,請分別將key
和value
替換為element
和count
宠哄。 立即運(yùn)行playground
壹将,您的precondition
塊將像以前一樣通過。
現(xiàn)在沒有人會知道您只是在使用Dictionary
為您辛苦工作毛嫉!
是時(shí)候?qū)⒛?code>Bag帶回家了诽俯。
Adopting the Collection Protocol
事不宜遲,這里是創(chuàng)建集合的真正內(nèi)容:集合(Collection)
協(xié)議! 重申一下暴区,集合Collection
是一個(gè)序列闯团,您可以按索引對其進(jìn)行訪問并多次遍歷。
要采用Collection
仙粱,您需要提供以下詳細(xì)信息:
- startIndex和endIndex:定義集合的邊界房交,并公開橫向的起點(diǎn)。
-
subscript (position:):允許使用索引訪問集合中的任何元素伐割。 此訪問應(yīng)以
O(1)
時(shí)間復(fù)雜度運(yùn)行候味。 - index(after :):在傳入索引之后立即返回索引。
擁有有效的收藏集僅需四個(gè)細(xì)節(jié)隔心。
在Sequence
擴(kuò)展之后添加以下代碼:
extension Bag: Collection {
// 1
typealias Index = DictionaryIndex<Element, Int>
// 2
var startIndex: Index {
return contents.startIndex
}
var endIndex: Index {
return contents.endIndex
}
// 3
subscript (position: Index) -> Iterator.Element {
precondition(indices.contains(position), "out of bounds")
let dictionaryElement = contents[position]
return (element: dictionaryElement.key,
count: dictionaryElement.value)
}
// 4
func index(after i: Index) -> Index {
return contents.index(after: i)
}
}
這很簡單白群。 在這里,您:
- 1) 將
Collection
中定義的Index
類型聲明為DictionaryIndex
硬霍。 您會將這些索引傳遞給內(nèi)容帜慢。 - 2) 從
contents
返回開始和結(jié)束索引。 - 3) 使用
precondition
來強(qiáng)制執(zhí)行有效索引唯卖。 您從該索引處的contents
返回值作為新的元組粱玲。 - 4) 返回在
contents
上調(diào)用的index(after :)
的值。
通過簡單地添加這些屬性和方法耐床,您就創(chuàng)建了一個(gè)功能齊全的集合密幔!
1. Testing Your Collection
將以下代碼添加到playground
的末尾以測試一些新功能:
// Get the first item in the bag
let firstItem = shoppingCart.first
precondition(
(firstItem!.element == "Orange" && firstItem!.count == 1) ||
(firstItem?.element == "Banana" && firstItem?.count == 2),
"Expected first item of shopping cart to be (\"Orange\", 1) or (\"Banana\", 2)")
// Check if the bag is empty
let isEmpty = shoppingCart.isEmpty
precondition(isEmpty == false,
"Expected shopping cart to not be empty")
// Get the number of unique items in the bag
let uniqueItems = shoppingCart.count
precondition(uniqueItems == 2,
"Expected shoppingCart to have 2 unique items")
// Find the first item with an element of "Banana"
let bananaIndex = shoppingCart.indices.first {
shoppingCart[$0].element == "Banana"
}!
let banana = shoppingCart[bananaIndex]
precondition(banana.element == "Banana" && banana.count == 2,
"Expected banana to have value (\"Banana\", 2)")
再次運(yùn)行playground
。 太棒了撩轰!
提示一下您對所做的事情感到非常滿意的那一刻胯甩,但感覺到即將出現(xiàn)"but wait, you can do better"
的評論……嗯,您是對的堪嫂! 你可以做得更好偎箫。 您的Bag
中仍有一些Dictionary
。
Improving Collection
Bag
又回到了太多的內(nèi)部運(yùn)作皆串。 Bag
的用戶需要使用DictionaryIndex
對象來訪問集合中的元素淹办。
您可以輕松解決此問題。 在Collection
擴(kuò)展名后面添加以下內(nèi)容:
// 1
struct BagIndex<Element: Hashable> {
// 2
fileprivate let index: DictionaryIndex<Element, Int>
// 3
fileprivate init(
_ dictionaryIndex: DictionaryIndex<Element, Int>) {
self.index = dictionaryIndex
}
}
在上面的代碼中恶复,您:
- 1) 定義一個(gè)新的通用類型
BagIndex
怜森。 像Bag
一樣,這需要可用于字典的Hashable
泛型類型谤牡。 - 2) 使該索引類型的基礎(chǔ)數(shù)據(jù)成為
DictionaryIndex
對象副硅。BagIndex
實(shí)際上只是一個(gè)包裝,將其真實(shí)索引對外界隱藏翅萤。 - 3) 創(chuàng)建一個(gè)接受
DictionaryIndex
進(jìn)行存儲的初始化程序恐疲。
現(xiàn)在,您需要考慮以下事實(shí),即Collection
要求Index
具有可比性培己,以允許比較兩個(gè)索引來執(zhí)行操作碳蛋。 因此,BagIndex
需要采用Comparable
省咨。
在BagIndex
之后添加以下擴(kuò)展名:
extension BagIndex: Comparable {
static func ==(lhs: BagIndex, rhs: BagIndex) -> Bool {
return lhs.index == rhs.index
}
static func <(lhs: BagIndex, rhs: BagIndex) -> Bool {
return lhs.index < rhs.index
}
}
這里的邏輯很簡單肃弟; 您正在使用DictionaryIndex
的等效方法返回正確的值。
Updating BagIndex
現(xiàn)在茸炒,您可以準(zhǔn)備將Bag
更新為使用BagIndex
愕乎。 將Collection
擴(kuò)展替換為以下內(nèi)容:
extension Bag: Collection {
// 1
typealias Index = BagIndex<Element>
var startIndex: Index {
// 2.1
return BagIndex(contents.startIndex)
}
var endIndex: Index {
// 2.2
return BagIndex(contents.endIndex)
}
subscript (position: Index) -> Iterator.Element {
precondition((startIndex ..< endIndex).contains(position),
"out of bounds")
// 3
let dictionaryElement = contents[position.index]
return (element: dictionaryElement.key,
count: dictionaryElement.value)
}
func index(after i: Index) -> Index {
// 4
return Index(contents.index(after: i.index))
}
}
每個(gè)帶編號的注釋都表示更改阵苇。它們是:
- 1) 將
Index
類型從DictionaryIndex
替換為BagIndex
壁公。 - 2) 從
startIndex
和endIndex
的contents
創(chuàng)建一個(gè)新的BagIndex
。 - 3) 使用
BagIndex
的index
屬性訪問contents
并從中返回元素绅项。 - 4) 使用
BagIndex
的屬性從內(nèi)容獲取DictionaryIndex
值紊册,并使用該值創(chuàng)建一個(gè)新的BagIndex
。
就這些快耿!用戶回到對存儲數(shù)據(jù)的方式一無所知囊陡。您還可能會更好地控制索引對象。
在總結(jié)之前掀亥,還有一個(gè)更重要的主題需要討論撞反。通過添加基于索引的訪問,您現(xiàn)在可以為集合中的一系列值建立索引搪花。是時(shí)候讓您了解slice
如何與集合一起工作了遏片。
Using Slices
slice
是視圖集合中元素的子序列。它使您無需復(fù)制就可以對元素的特定子序列執(zhí)行操作撮竿。
slice
存儲對創(chuàng)建它的基礎(chǔ)集合的引用吮便。slice
與它們的基本集合共享索引,保留對開始和結(jié)束索引的引用以標(biāo)記子序列范圍幢踏。slice
具有O(1)
空間復(fù)雜度髓需,因?yàn)樗鼈冎苯右闷浠炯稀?/p>
要查看其工作原理,請將以下代碼添加到playground
的末尾:
// 1
let fruitBasket = Bag(dictionaryLiteral:
("Apple", 5), ("Orange", 2), ("Pear", 3), ("Banana", 7))
// 2
let fruitSlice = fruitBasket.dropFirst()
// 3
if let fruitMinIndex = fruitSlice.indices.min(by:
{ fruitSlice[$0] > fruitSlice[$1] }) {
// 4
let basketElement = fruitBasket[fruitMinIndex]
let sliceElement = fruitSlice[fruitMinIndex]
precondition(basketElement == sliceElement,
"Expected basketElement and sliceElement to be the same element")
}
再次運(yùn)行playground
房蝉。
在上面的代碼中僚匆,您:
- 1) 創(chuàng)建一個(gè)由四個(gè)不同水果組成的水果籃。
- 2) 移除第一類水果搭幻。實(shí)際上咧擂,這只是在水果籃中創(chuàng)建一個(gè)新的
slice
視圖(不包括您刪除的第一個(gè)元素),而不是創(chuàng)建一個(gè)全新的Bag
對象粗卜。您會在結(jié)果欄中注意到這里的類型為Slice <Bag <String >>
屋确。 - 3) 在剩余的水果中找到最少出現(xiàn)的水果的索引。
- 4) 證明即使從
slice
計(jì)算索引,您也可以使用基礎(chǔ)集合和切片中的索引來檢索相同的元素攻臀。
注意:對于基于哈希的集合(例如
Dictionary
和Bag
)焕数,切片似乎沒什么用,因?yàn)樗鼈兊捻樞蛭匆匀魏斡幸饬x的方式定義刨啸。另一方面堡赔,Array
是集合類型的一個(gè)很好的例子,其中切片在執(zhí)行子序列操作中起著巨大的作用设联。
在本教程中善已,您學(xué)習(xí)了如何在Swift
中創(chuàng)建自定義集合。您對 Sequence离例,Collection换团,CustomStringConvertible,ExpressibleByArrayLiteral宫蛆,ExpressibleByDictionaryLiteral添加了一致性艘包,并創(chuàng)建了自己的索引類型。
如果您想查看或?yàn)楦暾?code>Bag實(shí)施做出貢獻(xiàn)耀盗,請查看Swift Algorithm Club implementation實(shí)施以及Foundation
實(shí)施NSCountedSet想虎。
這些只是Swift
提供的用于創(chuàng)建健壯和有用的集合類型的所有協(xié)議的一種體驗(yàn)。如果您想了解一些此處未涵蓋的內(nèi)容叛拷,請查看以下內(nèi)容:
您還可以查看有關(guān)Protocols in Swift的更多信息舌厨,并了解有關(guān)采用Swift標(biāo)準(zhǔn)庫中可用的通用協(xié)議adopting common protocols的更多信息。
后記
本篇主要講述了使用協(xié)議構(gòu)建自定義Collection忿薇,感興趣的給個(gè)贊或者關(guān)注~~~