定義一個協(xié)議時,聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分將會非常有用。關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,其代表的實際類型在協(xié)議被遵循時才會被指定。關(guān)聯(lián)類型通過 associatedtype 關(guān)鍵字來指定。
關(guān)聯(lián)類型實踐
下面例子定義了一個 Container 協(xié)議,該協(xié)議定義了一個關(guān)聯(lián)類型 Item:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container 協(xié)議定義了三個任何遵循該協(xié)議的類型(即容器)必須提供的功能:
- 必須可以通過
append(_:)方法添加一個新元素到容器里。 - 必須可以通過
count屬性獲取容器中元素的數(shù)量,并返回一個 Int 值。 - 必須可以通過索引值類型為
Int的下標(biāo)檢索到容器中的每一個元素。
該協(xié)議沒有指定容器中元素該如何存儲以及元素類型。該協(xié)議只指定了任何遵從 Container 協(xié)議的類型必須提供的三個功能。遵從協(xié)議的類型在滿足這三個條件的情況下,也可以提供其他額外的功能。
任何遵從 Container 協(xié)議的類型必須能夠指定其存儲的元素的類型。具體來說,它必須確保添加到容器內(nèi)的元素以及下標(biāo)返回的元素類型是正確的。
為了定義這些條件,Container 協(xié)議需要在不知道容器中元素的具體類型的情況下引用這種類型。Container 協(xié)議需要指定任何通過 append(_:) 方法添加到容器中的元素和容器內(nèi)的元素是相同類型,并且通過容器下標(biāo)返回的元素的類型也是這種類型。
為此,Container 協(xié)議聲明了一個關(guān)聯(lián)類型 Item,寫作 associatedtype Item。協(xié)議沒有定義 Item 是什么,這個信息留給遵從協(xié)議的類型來提供。盡管如此,Item 別名提供了一種方式來引用 Container 中元素的類型,并將之用于 append(_:) 方法和下標(biāo),從而保證任何 Container 的行為都能如預(yù)期。
這是前面非泛型版本 IntStack 類型,使其遵循 Container 協(xié)議:
struct IntStack: Container {
// IntStack 的原始實現(xiàn)部分
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 協(xié)議的實現(xiàn)部分
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 結(jié)構(gòu)體實現(xiàn)了 Container 協(xié)議的三個要求,其原有功能也不會和這些要求相沖突。
此外,IntStack 在實現(xiàn) Container 的要求時,指定 Item 為 Int 類型,即 typealias Item = Int,從而將 Container 協(xié)議中抽象的 Item 類型轉(zhuǎn)換為具體的 Int 類型。
由于 Swift 的類型推斷,實際上在 IntStack 的定義中不需要聲明 Item 為 Int。因為 IntStack 符合 Container 協(xié)議的所有要求,Swift 只需通過 append(_:) 方法的 item 參數(shù)類型和下標(biāo)返回值的類型,就可以推斷出 Item 的具體類型。事實上,如果你在上面的代碼中刪除了 typealias Item = Int 這一行,一切也可正常工作,因為 Swift 清楚地知道 Item 應(yīng)該是哪種類型。
你也可以讓泛型 Stack 結(jié)構(gòu)體遵循 Container 協(xié)議:
struct Stack<Element>: Container {
// Stack<Element> 的原始實現(xiàn)部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 協(xié)議的實現(xiàn)部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
這一次,占位類型參數(shù) Element 被用作 append(_:) 方法的 item 參數(shù)和下標(biāo)的返回類型。Swift 可以據(jù)此推斷出 Element 的類型即是 Item 的類型。
擴展現(xiàn)有類型來指定關(guān)聯(lián)類型
在擴展添加協(xié)議一致性 中描述了如何利用擴展讓一個已存在的類型遵循一個協(xié)議,這包括使用了關(guān)聯(lián)類型協(xié)議。
Swift 的 Array 類型已經(jīng)提供 append(_:) 方法,count 屬性,以及帶有 Int 索引的下標(biāo)來檢索其元素。這三個功能都符合 Container 協(xié)議的要求,也就意味著你只需聲明 Array 遵循Container 協(xié)議,就可以擴展 Array,使其遵從 Container 協(xié)議。你可以通過一個空擴展來實現(xiàn)這點,正如通過擴展采納協(xié)議中的描述:
extension Array: Container {}
Array 的 append(_:) 方法和下標(biāo)確保了 Swift 可以推斷出 Item 具體類型。定義了這個擴展后,你可以將任意 Array 當(dāng)作 Container 來使用。
給關(guān)聯(lián)類型添加約束
你可以在協(xié)議里給關(guān)聯(lián)類型添加約束來要求遵循的類型滿足約束。例如,下面的代碼定義了 Container 協(xié)議, 要求關(guān)聯(lián)類型 Item 必須遵循 Equatable 協(xié)議:
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
要遵守 Container 協(xié)議,Item 類型也必須遵守 Equatable 協(xié)議。
在關(guān)聯(lián)類型約束里使用協(xié)議
協(xié)議可以作為它自身的要求出現(xiàn)。例如,有一個協(xié)議細(xì)化了 Container 協(xié)議,添加了一個suffix(_:) 方法。suffix(_:) 方法返回容器中從后往前給定數(shù)量的元素,并把它們存儲在一個 Suffix 類型的實例里。
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
在這個協(xié)議里,Suffix 是一個關(guān)聯(lián)類型,就像上邊例子中 Container 的 Item 類型一樣。Suffix 擁有兩個約束:它必須遵循 SuffixableContainer 協(xié)議(就是當(dāng)前定義的協(xié)議),以及它的 Item 類型必須是和容器里的 Item 類型相同。Item 的約束是一個 where 分句,它在下面 具有泛型 Where 子句的擴展 中有討論。
這是上面 泛型類型 中 Stack 類型的擴展,它遵循了 SuffixableContainer 協(xié)議:
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// 推斷 suffix 結(jié)果是Stack。
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30
在上面的例子中,Suffix 是 Stack 的關(guān)聯(lián)類型,也是 Stack ,所以 Stack 的后綴運算返回另一個 Stack 。另外,遵循 SuffixableContainer 的類型可以擁有一個與它自己不同的 Suffix 類型——也就是說后綴運算可以返回不同的類型。比如說,這里有一個非泛型 IntStack 類型的擴展,它遵循了 SuffixableContainer 協(xié)議,使用 Stack<Int> 作為它的后綴類型而不是 IntStack:
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
}
// 推斷 suffix 結(jié)果是 Stack<Int>。
}