文中所描述的遵循協(xié)議的類型,之所以寫為類型是因為在Swift中遵循協(xié)議的可以是類,結(jié)構(gòu)體或者協(xié)議等等,官方文檔用type,所以文中寫為類型。
當(dāng)我們定義一個協(xié)議的時候,在里面聲明一個或多個關(guān)聯(lián)類型會很有幫助。關(guān)聯(lián)類型可以讓我們以占位符的形式在協(xié)議中定義一個類型。這個自定義的類型只有在該協(xié)議被遵循時才被特別指定。關(guān)聯(lián)類型的關(guān)鍵字是associatedtype
Associated Types基本使用
我們定義一個叫做Container的容器協(xié)議,里面聲明一個關(guān)聯(lián)類型叫Item:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
這個容器協(xié)議定義了三個必須被實現(xiàn)的方法:
- 這個容器可以通過
append(_:)來添加數(shù)據(jù) - 通過count屬性我們可以獲取這個容器中數(shù)據(jù)的個數(shù)
- 我們可以通過下標(biāo)來獲取這個容器中的每個數(shù)據(jù)
這個容器協(xié)議沒有特別指名其中的數(shù)據(jù)是如何被存儲的,以及具體的數(shù)據(jù)類型。這個容器協(xié)議只是特別定義了三個必須遵循的方法。只要能實現(xiàn)這三個方法,那么遵循協(xié)議了的類型也可以提供更多的其他方法。
任何遵循了這個容器協(xié)議的類型中必須指明這個Item是哪個類型。這里,可以通過特別指定來保證加入容器的每個對象的類型是正確的,同時也保證了當(dāng)用下標(biāo)取出的對象的類型也是正確的。
為了滿足這個需求,Container協(xié)議需要使用一種方式來指定容器中存儲的這些數(shù)據(jù)的類型,但該類型在協(xié)議中聲明是并不能指定為具體類型。而且在協(xié)議中的append(_:)方法中參數(shù)以及subscript(i: Int) -> Item返回值的類型必須一致。
為了達(dá)到這個目的,Container協(xié)議聲明了一個關(guān)聯(lián)類型Item,寫作associatedtype Item,這個協(xié)議并沒有指明Item是什么,這個將留給遵循該協(xié)議的類來指定。雖然如此,但這個Item指定了容器協(xié)議中每個數(shù)據(jù)的類型,從以上兩個方法可以看出,它保證了每個容器類型中的數(shù)據(jù)類型是一致的。
看一下遵循了該容器協(xié)議的具體類:
struct IntStack: Container {
// original IntStack implementation
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
這個IntStack類型實現(xiàn)了容器協(xié)議的所有需求。而且,IntStack也明確指定了Item其實就是Int類型。typealias Item = Int這句代碼就是將協(xié)議中的抽象類型變?yōu)榫唧w類型。
由于Swift的類型推斷,我們也不必特別指名Item是Int類型,因為IntStack遵循了容器協(xié)議,所以簡單的從append(_:)方法和subscript(i: Int) -> Item方法中可以推斷出Item就是Int,所以我們可以刪除typealias Item = Int這行代碼。
當(dāng)然,我們也可以將Stack做成范型,如下:
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
現(xiàn)在,從append(_:)方法和subscript(i: Int) -> Item方法中可以看出使用的是Element,因此Swift可以推斷出關(guān)聯(lián)類型Item這回是Element。
擴(kuò)展一個指定類型來指定這個關(guān)聯(lián)類型
我們可以擴(kuò)展一個已存在的類型來遵循這個聲明了關(guān)聯(lián)類型的容器協(xié)議。
Swift的Array類型已經(jīng)提供了append(_:)方法,一個count屬性,以及通過下標(biāo)返回元素的方法。這也就是說我們可以擴(kuò)展Array類型使其遵循該容器協(xié)議。如下
extension Array: Container {}
Array中已存在的方法append(_:)和下標(biāo)返回方法使得Swift的類型判斷可以明確Item的類型,就和之前Stack一樣。這樣擴(kuò)展了Array之后,我們就可以把Array和Stack一起當(dāng)作Container來使用。
對關(guān)聯(lián)類型添加約束
我們可以在協(xié)議中對關(guān)聯(lián)類型進(jìn)行類型約束,使得遵循該協(xié)議的類型同樣受到這個約束。如下,我們可以看到,遵循這個版本的Container的類型必須保證item是一樣的。
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
拿協(xié)議自身來作為協(xié)議內(nèi)關(guān)聯(lián)類型的約束
再從上面引深,我們可以將一個協(xié)議作為滿足自身需求來約束關(guān)聯(lián)類型。來看下下面這個例子,定義了一個遵循容器協(xié)議的協(xié)議,并添加了suffix(_:)方法。這個方法就是將從指定的下標(biāo)開始到末尾的數(shù)據(jù)裝到一個叫做Suffix的容器里并返回。
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
在這個協(xié)議里面,Suffix就和之前的Item一樣是關(guān)聯(lián)類型。
Suffix有兩個約束:
- 必須遵循
SuffixableContainer協(xié)議 - 該協(xié)議內(nèi)的
Item和Container中的Item必須一致
接下來讓我們看下遵循了該協(xié)議的Stack是怎么寫的:
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
從上面的例子可以看出,Stack中Suffix關(guān)聯(lián)類型就是Stack自身(別忘了,Stack是個范型哦),所以suffix(_:)操作將返回另一個Stack。然而,我們也可以在遵循了SuffixableContainer協(xié)議的同時使Suffix關(guān)聯(lián)類型不是自身,也就是說,我們可以在suffix(_:)方法返回一個不同的類型,來看下下面這個例子,我們用一個非范型類型IntStack來遵循SuffixableContainer協(xié)議,但是我們把關(guān)聯(lián)類型IntStack改為范型Stack<Int>
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack<Int>.
}