1.0 翻譯:takalard 校對(duì):lifedim
2.0 翻譯+校對(duì): SergioChan
2.1 校對(duì):shanks,2015-11-01
2.2:翻譯+校對(duì):Lanford,2016-04-08 SketchK 2016-05-16
3.0:翻譯+校對(duì):chenmingjia,2016-09-12
3.0.1,shanks,2016-11-133.1:翻譯:qhd,2017-04-10
4.0 翻譯+校對(duì):kemchenj 2017-09-21
本頁(yè)包含內(nèi)容:
泛型代碼讓你能夠根據(jù)自定義的需求,編寫(xiě)出適用于任意類(lèi)型、靈活可重用的函數(shù)及類(lèi)型。它能讓你避免代碼的重復(fù),用一種清晰和抽象的方式來(lái)表達(dá)代碼的意圖。
泛型是 Swift 最強(qiáng)大的特性之一,許多 Swift 標(biāo)準(zhǔn)庫(kù)是通過(guò)泛型代碼構(gòu)建的。事實(shí)上,泛型的使用貫穿了整本語(yǔ)言手冊(cè),只是你可能沒(méi)有發(fā)現(xiàn)而已。例如,Swift 的 Array
和 Dictionary
都是泛型集合。你可以創(chuàng)建一個(gè) Int
數(shù)組,也可創(chuàng)建一個(gè) String
數(shù)組,甚至可以是任意其他 Swift 類(lèi)型的數(shù)組。同樣的,你也可以創(chuàng)建存儲(chǔ)任意指定類(lèi)型的字典。
下面是一個(gè)標(biāo)準(zhǔn)的非泛型函數(shù) swapTwoInts(_:_:)
,用來(lái)交換兩個(gè) Int
值:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
這個(gè)函數(shù)使用輸入輸出參數(shù)(inout
)來(lái)交換 a
和 b
的值,請(qǐng)參考輸入輸出參數(shù)。
swapTwoInts(_:_:)
函數(shù)交換 b
的原始值到 a
,并交換 a
的原始值到 b
。你可以調(diào)用這個(gè)函數(shù)交換兩個(gè) Int
變量的值:
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印 “someInt is now 107, and anotherInt is now 3”
誠(chéng)然,swapTwoInts(_:_:)
函數(shù)挺有用,但是它只能交換 Int
值,如果你想要交換兩個(gè) String
值或者 Double
值,就不得不寫(xiě)更多的函數(shù),例如 swapTwoStrings(_:_:)
和 swapTwoDoubles(_:_:)
,如下所示:
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
你可能注意到 swapTwoInts(_:_:)
、swapTwoStrings(_:_:)
和 swapTwoDoubles(_:_:)
的函數(shù)功能都是相同的,唯一不同之處就在于傳入的變量類(lèi)型不同,分別是 Int
、String
和 Double
。
在實(shí)際應(yīng)用中,通常需要一個(gè)更實(shí)用更靈活的函數(shù)來(lái)交換兩個(gè)任意類(lèi)型的值,幸運(yùn)的是,泛型代碼幫你解決了這種問(wèn)題。(這些函數(shù)的泛型版本已經(jīng)在下面定義好了。)
注意
在上面三個(gè)函數(shù)中,a
和b
類(lèi)型必須相同。如果a
和b
類(lèi)型不同,那它們倆就不能互換值。Swift 是類(lèi)型安全的語(yǔ)言,所以它不允許一個(gè)String
類(lèi)型的變量和一個(gè)Double
類(lèi)型的變量互換值。試圖這樣做將導(dǎo)致編譯錯(cuò)誤。
泛型函數(shù)可以適用于任何類(lèi)型,下面的 swapTwoValues(_:_:)
函數(shù)是上面三個(gè)函數(shù)的泛型版本:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(_:_:)
的函數(shù)主體和 swapTwoInts(_:_:)
函數(shù)是一樣的,它們只在第一行有點(diǎn)不同,如下所示:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
這個(gè)函數(shù)的泛型版本使用了占位類(lèi)型名(在這里用字母 T
來(lái)表示)來(lái)代替實(shí)際類(lèi)型名(例如 Int
、String
或 Double
)。占位類(lèi)型名沒(méi)有指明 T
必須是什么類(lèi)型,但是它指明了 a
和 b
必須是同一類(lèi)型 T
,無(wú)論 T
代表什么類(lèi)型。只有 swapTwoValues(_:_:)
函數(shù)在調(diào)用時(shí),才會(huì)根據(jù)所傳入的實(shí)際類(lèi)型決定 T
所代表的類(lèi)型。
泛型函數(shù)和非泛型函數(shù)的另外一個(gè)不同之處,在于這個(gè)泛型函數(shù)名(swapTwoValues(_:_:)
)后面跟著占位類(lèi)型名(T
),并用尖括號(hào)括起來(lái)(<T>
)。這個(gè)尖括號(hào)告訴 Swift 那個(gè) T
是 swapTwoValues(_:_:)
函數(shù)定義內(nèi)的一個(gè)占位類(lèi)型名,因此 Swift 不會(huì)去查找名為 T
的實(shí)際類(lèi)型。
swapTwoValues(_:_:)
函數(shù)現(xiàn)在可以像 swapTwoInts(_:_:)
那樣調(diào)用,不同的是它能接受兩個(gè)任意類(lèi)型的值,條件是這兩個(gè)值有著相同的類(lèi)型。swapTwoValues(_:_:)
函數(shù)被調(diào)用時(shí),T
所代表的類(lèi)型都會(huì)由傳入的值的類(lèi)型推斷出來(lái)。
在下面的兩個(gè)例子中,T
分別代表 Int
和 String
:
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 現(xiàn)在 107, and anotherInt 現(xiàn)在 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 現(xiàn)在 "world", and anotherString 現(xiàn)在 "hello"
注意
上面定義的swapTwoValues(_:_:)
函數(shù)是受swap(_:_:)
函數(shù)啟發(fā)而實(shí)現(xiàn)的。后者存在于 Swift 標(biāo)準(zhǔn)庫(kù),你可以在你的應(yīng)用程序中使用它。如果你在代碼中需要類(lèi)似swapTwoValues(_:_:)
函數(shù)的功能,你可以使用已存在的swap(_:_:)
函數(shù)。
在上面的 swapTwoValues(_:_:)
例子中,占位類(lèi)型 T
是類(lèi)型參數(shù)的一個(gè)例子。類(lèi)型參數(shù)指定并命名一個(gè)占位類(lèi)型,并且緊隨在函數(shù)名后面,使用一對(duì)尖括號(hào)括起來(lái)(例如 <T>
)。
一旦一個(gè)類(lèi)型參數(shù)被指定,你可以用它來(lái)定義一個(gè)函數(shù)的參數(shù)類(lèi)型(例如 swapTwoValues(_:_:)
函數(shù)中的參數(shù) a
和 b
),或者作為函數(shù)的返回類(lèi)型,還可以用作函數(shù)主體中的注釋類(lèi)型。在這些情況下,類(lèi)型參數(shù)會(huì)在函數(shù)調(diào)用時(shí)被實(shí)際類(lèi)型所替換。(在上面的 swapTwoValues(_:_:)
例子中,當(dāng)函數(shù)第一次被調(diào)用時(shí),T
被 Int
替換,第二次調(diào)用時(shí),被 String
替換。)
你可提供多個(gè)類(lèi)型參數(shù),將它們都寫(xiě)在尖括號(hào)中,用逗號(hào)分開(kāi)。
在大多數(shù)情況下,類(lèi)型參數(shù)具有一個(gè)描述性名字,例如 Dictionary<Key, Value>
中的 Key
和 Value
,以及 Array<Element>
中的 Element
,這可以告訴閱讀代碼的人這些類(lèi)型參數(shù)和泛型函數(shù)之間的關(guān)系。然而,當(dāng)它們之間沒(méi)有有意義的關(guān)系時(shí),通常使用單個(gè)字母來(lái)命名,例如 T
、U
、V
,正如上面演示的 swapTwoValues(_:_:)
函數(shù)中的 T
一樣。
注意
請(qǐng)始終使用大寫(xiě)字母開(kāi)頭的駝峰命名法(例如T
和MyTypeParameter
)來(lái)為類(lèi)型參數(shù)命名,以表明它們是占位類(lèi)型,而不是一個(gè)值。
除了泛型函數(shù),Swift 還允許你定義泛型類(lèi)型。這些自定義類(lèi)、結(jié)構(gòu)體和枚舉可以適用于任何類(lèi)型,類(lèi)似于 Array
和 Dictionary
。
這部分內(nèi)容將向你展示如何編寫(xiě)一個(gè)名為 Stack
(棧)的泛型集合類(lèi)型。棧是一系列值的有序集合,和 Array
類(lèi)似,但它相比 Swift 的 Array
類(lèi)型有更多的操作限制。數(shù)組允許在數(shù)組的任意位置插入新元素或是刪除其中任意位置的元素。而棧只允許在集合的末端添加新的元素(稱之為入棧)。類(lèi)似的,棧也只能從末端移除元素(稱之為出棧)。
注意
棧的概念已被UINavigationController
類(lèi)用來(lái)構(gòu)造視圖控制器的導(dǎo)航結(jié)構(gòu)。你通過(guò)調(diào)用UINavigationController
的pushViewController(_:animated:)
方法來(lái)添加新的視圖控制器到導(dǎo)航棧,通過(guò)popViewControllerAnimated(_:)
方法來(lái)從導(dǎo)航棧中移除視圖控制器。每當(dāng)你需要一個(gè)嚴(yán)格的“后進(jìn)先出”方式來(lái)管理集合,棧都是最實(shí)用的模型。
下圖展示了一個(gè)棧的入棧(push)和出棧(pop)的行為:
下面展示了如何編寫(xiě)一個(gè)非泛型版本的棧,以 Int
型的棧為例:
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
這個(gè)結(jié)構(gòu)體在棧中使用一個(gè)名為 items
的 Array
屬性來(lái)存儲(chǔ)值。Stack
提供了兩個(gè)方法:push(_:)
和 pop()
,用來(lái)向棧中壓入值以及從棧中移除值。這些方法被標(biāo)記為 mutating
,因?yàn)樗鼈冃枰薷慕Y(jié)構(gòu)體的 items
數(shù)組。
上面的 IntStack
結(jié)構(gòu)體只能用于 Int
類(lèi)型。不過(guò),可以定義一個(gè)泛型 Stack
結(jié)構(gòu)體,從而能夠處理任意類(lèi)型的值。
下面是相同代碼的泛型版本:
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
注意,Stack
基本上和 IntStack
相同,只是用占位類(lèi)型參數(shù) Element
代替了實(shí)際的 Int
類(lèi)型。這個(gè)類(lèi)型參數(shù)包裹在緊隨結(jié)構(gòu)體名的一對(duì)尖括號(hào)里(<Element>
)。
Element
為待提供的類(lèi)型定義了一個(gè)占位名。這種待提供的類(lèi)型可以在結(jié)構(gòu)體的定義中通過(guò) Element
來(lái)引用。在這個(gè)例子中,Element
在如下三個(gè)地方被用作占位符:
items
屬性,使用 Element
類(lèi)型的空數(shù)組對(duì)其進(jìn)行初始化。push(_:)
方法的唯一參數(shù) item
的類(lèi)型必須是 Element
類(lèi)型。pop()
方法的返回值類(lèi)型必須是 Element
類(lèi)型。由于 Stack
是泛型類(lèi)型,因此可以用來(lái)創(chuàng)建 Swift 中任意有效類(lèi)型的棧,就像 Array
和 Dictionary
那樣。
你可以通過(guò)在尖括號(hào)中寫(xiě)出棧中需要存儲(chǔ)的數(shù)據(jù)類(lèi)型來(lái)創(chuàng)建并初始化一個(gè) Stack
實(shí)例。例如,要?jiǎng)?chuàng)建一個(gè) String
類(lèi)型的棧,可以寫(xiě)成 Stack<String>()
:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 棧中現(xiàn)在有 4 個(gè)字符串
下圖展示了 stackOfStrings
如何將這四個(gè)值入棧:
移除并返回棧頂部的值 "cuatro"
,即將其出棧:
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值為 "cuatro",現(xiàn)在棧中還有 3 個(gè)字符串
下圖展示了 stackOfStrings
如何將頂部的值出棧:
當(dāng)你擴(kuò)展一個(gè)泛型類(lèi)型的時(shí)候,你并不需要在擴(kuò)展的定義中提供類(lèi)型參數(shù)列表。原始類(lèi)型定義中聲明的類(lèi)型參數(shù)列表在擴(kuò)展中可以直接使用,并且這些來(lái)自原始類(lèi)型中的參數(shù)名稱會(huì)被用作原始定義中類(lèi)型參數(shù)的引用。
下面的例子擴(kuò)展了泛型類(lèi)型 Stack
,為其添加了一個(gè)名為 topItem
的只讀計(jì)算型屬性,它將會(huì)返回當(dāng)前棧頂端的元素而不會(huì)將其從棧中移除:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
topItem
屬性會(huì)返回一個(gè) Element
類(lèi)型的可選值。當(dāng)棧為空的時(shí)候,topItem
會(huì)返回 nil
;當(dāng)棧不為空的時(shí)候,topItem
會(huì)返回 items
數(shù)組中的最后一個(gè)元素。
注意,這個(gè)擴(kuò)展并沒(méi)有定義一個(gè)類(lèi)型參數(shù)列表。相反的,Stack
類(lèi)型已有的類(lèi)型參數(shù)名稱 Element
,被用在擴(kuò)展中來(lái)表示計(jì)算型屬性 topItem
的可選類(lèi)型。
計(jì)算型屬性 topItem
現(xiàn)在可以用來(lái)訪問(wèn)任意 Stack
實(shí)例的頂端元素且不移除它:
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// 打印 “The top item on the stack is tres.”
swapTwoValues(_:_:)
函數(shù)和 Stack
類(lèi)型可以作用于任何類(lèi)型。不過(guò),有的時(shí)候如果能將使用在泛型函數(shù)和泛型類(lèi)型中的類(lèi)型添加一個(gè)特定的類(lèi)型約束,將會(huì)是非常有用的。類(lèi)型約束可以指定一個(gè)類(lèi)型參數(shù)必須繼承自指定類(lèi),或者符合一個(gè)特定的協(xié)議或協(xié)議組合。
例如,Swift 的 Dictionary
類(lèi)型對(duì)字典的鍵的類(lèi)型做了些限制。在字典的描述中,字典的鍵的類(lèi)型必須是可哈希(hashable
)的。也就是說(shuō),必須有一種方法能夠唯一地表示它。Dictionary
的鍵之所以要是可哈希的,是為了便于檢查字典是否已經(jīng)包含某個(gè)特定鍵的值。若沒(méi)有這個(gè)要求,Dictionary
將無(wú)法判斷是否可以插入或者替換某個(gè)指定鍵的值,也不能查找到已經(jīng)存儲(chǔ)在字典中的指定鍵的值。
為了實(shí)現(xiàn)這個(gè)要求,一個(gè)類(lèi)型約束被強(qiáng)制加到 Dictionary
的鍵類(lèi)型上,要求其鍵類(lèi)型必須符合 Hashable
協(xié)議,這是 Swift 標(biāo)準(zhǔn)庫(kù)中定義的一個(gè)特定協(xié)議。所有的 Swift 基本類(lèi)型(例如 String
、Int
、Double
和 Bool
)默認(rèn)都是可哈希的。
當(dāng)你創(chuàng)建自定義泛型類(lèi)型時(shí),你可以定義你自己的類(lèi)型約束,這些約束將提供更為強(qiáng)大的泛型編程能力。抽象概念,例如可哈希的,描述的是類(lèi)型在概念上的特征,而不是它們的顯式類(lèi)型。
你可以在一個(gè)類(lèi)型參數(shù)名后面放置一個(gè)類(lèi)名或者協(xié)議名,并用冒號(hào)進(jìn)行分隔,來(lái)定義類(lèi)型約束,它們將成為類(lèi)型參數(shù)列表的一部分。對(duì)泛型函數(shù)添加類(lèi)型約束的基本語(yǔ)法如下所示(作用于泛型類(lèi)型時(shí)的語(yǔ)法與之相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數(shù)的函數(shù)體部分
}
上面這個(gè)函數(shù)有兩個(gè)類(lèi)型參數(shù)。第一個(gè)類(lèi)型參數(shù) T
,有一個(gè)要求 T
必須是 SomeClass
子類(lèi)的類(lèi)型約束;第二個(gè)類(lèi)型參數(shù) U
,有一個(gè)要求 U
必須符合 SomeProtocol
協(xié)議的類(lèi)型約束。
這里有個(gè)名為 findIndex(ofString:in:)
的非泛型函數(shù),該函數(shù)的功能是在一個(gè) String
數(shù)組中查找給定 String
值的索引。若查找到匹配的字符串,findIndex(ofString:in:)
函數(shù)返回該字符串在數(shù)組中的索引值,否則返回 nil
:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
findIndex(ofString:in:)
函數(shù)可以用于查找字符串?dāng)?shù)組中的某個(gè)字符串:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// 打印 “The index of llama is 2”
如果只能查找字符串在數(shù)組中的索引,用處不是很大。不過(guò),你可以用占位類(lèi)型 T
替換 String
類(lèi)型來(lái)寫(xiě)出具有相同功能的泛型函數(shù) findIndex(_:_:)
。
下面展示了 findIndex(ofString:in:)
函數(shù)的泛型版本 findIndex(ofString:in:)
。請(qǐng)注意這個(gè)函數(shù)返回值的類(lèi)型仍然是 Int?
,這是因?yàn)楹瘮?shù)返回的是一個(gè)可選的索引數(shù),而不是從數(shù)組中得到的一個(gè)可選值。需要提醒的是,這個(gè)函數(shù)無(wú)法通過(guò)編譯,原因會(huì)在例子后面說(shuō)明:
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
上面所寫(xiě)的函數(shù)無(wú)法通過(guò)編譯。問(wèn)題出在相等性檢查上,即 "if value == valueToFind
"。不是所有的 Swift 類(lèi)型都可以用等式符(==
)進(jìn)行比較。比如說(shuō),如果你創(chuàng)建一個(gè)自定義的類(lèi)或結(jié)構(gòu)體來(lái)表示一個(gè)復(fù)雜的數(shù)據(jù)模型,那么 Swift 無(wú)法猜到對(duì)于這個(gè)類(lèi)或結(jié)構(gòu)體而言“相等”意味著什么。正因如此,這部分代碼無(wú)法保證適用于每個(gè)可能的類(lèi)型 T
,當(dāng)你試圖編譯這部分代碼時(shí)會(huì)出現(xiàn)相應(yīng)的錯(cuò)誤。
不過(guò),所有的這些并不會(huì)讓我們無(wú)從下手。Swift 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè) Equatable
協(xié)議,該協(xié)議要求任何遵循該協(xié)議的類(lèi)型必須實(shí)現(xiàn)等式符(==
)及不等符(!=
),從而能對(duì)該類(lèi)型的任意兩個(gè)值進(jìn)行比較。所有的 Swift 標(biāo)準(zhǔn)類(lèi)型自動(dòng)支持 Equatable
協(xié)議。
任何 Equatable
類(lèi)型都可以安全地使用在 findIndex(of:in:)
函數(shù)中,因?yàn)槠浔WC支持等式操作符。為了說(shuō)明這個(gè)事實(shí),當(dāng)你定義一個(gè)函數(shù)時(shí),你可以定義一個(gè) Equatable
類(lèi)型約束作為類(lèi)型參數(shù)定義的一部分:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
findIndex(of:in:)
唯一的類(lèi)型參數(shù)寫(xiě)做 T: Equatable
,也就意味著“任何符合 Equatable
協(xié)議的類(lèi)型 T
”。
findIndex(of:in:)
函數(shù)現(xiàn)在可以成功編譯了,并且可以作用于任何符合 Equatable
的類(lèi)型,如 Double
或 String
:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 類(lèi)型為 Int?,其值為 nil,因?yàn)?9.3 不在數(shù)組中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 類(lèi)型為 Int?,其值為 2
定義一個(gè)協(xié)議時(shí),有的時(shí)候聲明一個(gè)或多個(gè)關(guān)聯(lián)類(lèi)型作為協(xié)議定義的一部分將會(huì)非常有用。關(guān)聯(lián)類(lèi)型為協(xié)議中的某個(gè)類(lèi)型提供了一個(gè)占位名(或者說(shuō)別名),其代表的實(shí)際類(lèi)型在協(xié)議被采納時(shí)才會(huì)被指定。你可以通過(guò) associatedtype
關(guān)鍵字來(lái)指定關(guān)聯(lián)類(lèi)型。
下面例子定義了一個(gè) Container
協(xié)議,該協(xié)議定義了一個(gè)關(guān)聯(lián)類(lèi)型 ItemType
:
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
Container
協(xié)議定義了三個(gè)任何采納了該協(xié)議的類(lèi)型(即容器)必須提供的功能:
append(_:)
方法添加一個(gè)新元素到容器里。count
屬性獲取容器中元素的數(shù)量,并返回一個(gè) Int
值。Int
的下標(biāo)檢索到容器中的每一個(gè)元素。這個(gè)協(xié)議沒(méi)有指定容器中元素該如何存儲(chǔ),以及元素必須是何種類(lèi)型。這個(gè)協(xié)議只指定了三個(gè)任何遵從 Container
協(xié)議的類(lèi)型必須提供的功能。遵從協(xié)議的類(lèi)型在滿足這三個(gè)條件的情況下也可以提供其他額外的功能。
任何遵從 Container
協(xié)議的類(lèi)型必須能夠指定其存儲(chǔ)的元素的類(lèi)型,必須保證只有正確類(lèi)型的元素可以加進(jìn)容器中,必須明確通過(guò)其下標(biāo)返回的元素的類(lèi)型。
為了定義這三個(gè)條件,Container
協(xié)議需要在不知道容器中元素的具體類(lèi)型的情況下引用這種類(lèi)型。Container
協(xié)議需要指定任何通過(guò) append(_:)
方法添加到容器中的元素和容器中的元素是相同類(lèi)型,并且通過(guò)容器下標(biāo)返回的元素的類(lèi)型也是這種類(lèi)型。
為了達(dá)到這個(gè)目的,Container
協(xié)議聲明了一個(gè)關(guān)聯(lián)類(lèi)型 ItemType
,寫(xiě)作 associatedtype ItemType
。這個(gè)協(xié)議無(wú)法定義 ItemType
是什么類(lèi)型的別名,這個(gè)信息將留給遵從協(xié)議的類(lèi)型來(lái)提供。盡管如此,ItemType
別名提供了一種方式來(lái)引用 Container
中元素的類(lèi)型,并將之用于 append(_:)
方法和下標(biāo),從而保證任何 Container
的行為都能夠正如預(yù)期地被執(zhí)行。
下面是先前的非泛型的 IntStack
類(lèi)型,這一版本采納并符合了 Container
協(xié)議:
struct IntStack: Container {
// IntStack 的原始實(shí)現(xiàn)部分
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 協(xié)議的實(shí)現(xiàn)部分
typealias ItemType = 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)體實(shí)現(xiàn)了 Container
協(xié)議的三個(gè)要求,其原有功能也不會(huì)和這些要求相沖突。
此外,IntStack
在實(shí)現(xiàn) Container
的要求時(shí),指定 ItemType
為 Int
類(lèi)型,即 typealias ItemType = Int
,從而將 Container
協(xié)議中抽象的 ItemType
類(lèi)型轉(zhuǎn)換為具體的 Int
類(lèi)型。
由于 Swift 的類(lèi)型推斷,你實(shí)際上不用在 IntStack
的定義中聲明 ItemType
為 Int
。因?yàn)?IntStack
符合 Container
協(xié)議的所有要求,Swift 只需通過(guò) append(_:)
方法的 item
參數(shù)類(lèi)型和下標(biāo)返回值的類(lèi)型,就可以推斷出 ItemType
的具體類(lèi)型。事實(shí)上,如果你在上面的代碼中刪除了 typealias ItemType = Int
這一行,一切仍舊可以正常工作,因?yàn)?Swift 清楚地知道 ItemType
應(yīng)該是哪種類(lèi)型。
你也可以讓泛型 Stack
結(jié)構(gòu)體遵從 Container
協(xié)議:
struct Stack<Element>: Container {
// Stack<Element> 的原始實(shí)現(xiàn)部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 協(xié)議的實(shí)現(xiàn)部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
這一次,占位類(lèi)型參數(shù) Element
被用作 append(_:)
方法的 item
參數(shù)和下標(biāo)的返回類(lèi)型。Swift 可以據(jù)此推斷出 Element
的類(lèi)型即是 ItemType
的類(lèi)型。
通過(guò)擴(kuò)展添加協(xié)議一致性中描述了如何利用擴(kuò)展讓一個(gè)已存在的類(lèi)型符合一個(gè)協(xié)議,這包括使用了關(guān)聯(lián)類(lèi)型的協(xié)議。
Swift 的 Array
類(lèi)型已經(jīng)提供 append(_:)
方法,一個(gè) count
屬性,以及一個(gè)接受 Int
類(lèi)型索引值的下標(biāo)用以檢索其元素。這三個(gè)功能都符合 Container
協(xié)議的要求,也就意味著你只需簡(jiǎn)單地聲明 Array
采納該協(xié)議就可以擴(kuò)展 Array
,使其遵從 Container
協(xié)議。你可以通過(guò)一個(gè)空擴(kuò)展來(lái)實(shí)現(xiàn)這點(diǎn),正如通過(guò)擴(kuò)展采納協(xié)議中的描述:
extension Array: Container {}
如同上面的泛型 Stack
結(jié)構(gòu)體一樣,Array
的 append(_:)
方法和下標(biāo)確保了 Swift 可以推斷出 ItemType
的類(lèi)型。定義了這個(gè)擴(kuò)展后,你可以將任意 Array
當(dāng)作 Container
來(lái)使用。
你可以給協(xié)議里的關(guān)聯(lián)類(lèi)型添加類(lèi)型注釋,讓遵守協(xié)議的類(lèi)型必須遵循這個(gè)約束條件。例如,下面的代碼定義了一個(gè) Item
必須遵循 Equatable
的 Container
類(lèi)型:
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
為了遵守了 Container
協(xié)議,Item 類(lèi)型也必須遵守 Equatable
協(xié)議。
類(lèi)型約束讓你能夠?yàn)榉盒秃瘮?shù),下標(biāo),類(lèi)型的類(lèi)型參數(shù)定義一些強(qiáng)制要求。
為關(guān)聯(lián)類(lèi)型定義約束也是非常有用的。你可以在參數(shù)列表中通過(guò) where
子句為關(guān)聯(lián)類(lèi)型定義約束。你能通過(guò) where
子句要求一個(gè)關(guān)聯(lián)類(lèi)型遵從某個(gè)特定的協(xié)議,以及某個(gè)特定的類(lèi)型參數(shù)和關(guān)聯(lián)類(lèi)型必須類(lèi)型相同。你可以通過(guò)將 where
關(guān)鍵字緊跟在類(lèi)型參數(shù)列表后面來(lái)定義 where
子句,where
子句后跟一個(gè)或者多個(gè)針對(duì)關(guān)聯(lián)類(lèi)型的約束,以及一個(gè)或多個(gè)類(lèi)型參數(shù)和關(guān)聯(lián)類(lèi)型間的相等關(guān)系。你可以在函數(shù)體或者類(lèi)型的大括號(hào)之前添加 where 子句。
下面的例子定義了一個(gè)名為 allItemsMatch
的泛型函數(shù),用來(lái)檢查兩個(gè) Container
實(shí)例是否包含相同順序的相同元素。如果所有的元素能夠匹配,那么返回 true
,否則返回 false
。
被檢查的兩個(gè) Container
可以不是相同類(lèi)型的容器(雖然它們可以相同),但它們必須擁有相同類(lèi)型的元素。這個(gè)要求通過(guò)一個(gè)類(lèi)型約束以及一個(gè) where
子句來(lái)表示:
func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 檢查兩個(gè)容器含有相同數(shù)量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 檢查每一對(duì)元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
這個(gè)函數(shù)接受 someContainer
和 anotherContainer
兩個(gè)參數(shù)。參數(shù) someContainer
的類(lèi)型為 C1
,參數(shù) anotherContainer
的類(lèi)型為 C2
。C1
和 C2
是容器的兩個(gè)占位類(lèi)型參數(shù),函數(shù)被調(diào)用時(shí)才能確定它們的具體類(lèi)型。
這個(gè)函數(shù)的類(lèi)型參數(shù)列表還定義了對(duì)兩個(gè)類(lèi)型參數(shù)的要求:
C1
必須符合 Container
協(xié)議(寫(xiě)作 C1: Container
)。C2
必須符合 Container
協(xié)議(寫(xiě)作 C2: Container
)。C1
的 ItemType
必須和 C2
的 ItemType
類(lèi)型相同(寫(xiě)作 C1.ItemType == C2.ItemType
)。C1
的 ItemType
必須符合 Equatable
協(xié)議(寫(xiě)作 C1.ItemType: Equatable
)。第三個(gè)和第四個(gè)要求被定義為一個(gè) where
子句,寫(xiě)在關(guān)鍵字 where
后面,它們也是泛型函數(shù)類(lèi)型參數(shù)列表的一部分。
這些要求意味著:
someContainer
是一個(gè) C1
類(lèi)型的容器。anotherContainer
是一個(gè) C2
類(lèi)型的容器。someContainer
和 anotherContainer
包含相同類(lèi)型的元素。someContainer
中的元素可以通過(guò)不等于操作符(!=
)來(lái)檢查它們是否彼此不同。第三個(gè)和第四個(gè)要求結(jié)合起來(lái)意味著 anotherContainer
中的元素也可以通過(guò) !=
操作符來(lái)比較,因?yàn)樗鼈兒?someContainer
中的元素類(lèi)型相同。
這些要求讓 allItemsMatch(_:_:)
函數(shù)能夠比較兩個(gè)容器,即使它們的容器類(lèi)型不同。
allItemsMatch(_:_:)
函數(shù)首先檢查兩個(gè)容器是否擁有相同數(shù)量的元素,如果它們的元素?cái)?shù)量不同,那么一定不匹配,函數(shù)就會(huì)返回 false
。
進(jìn)行這項(xiàng)檢查之后,通過(guò) for-in
循環(huán)和半閉區(qū)間操作符(..<
)來(lái)迭代每個(gè)元素,檢查 someContainer
中的元素是否不等于 anotherContainer
中的對(duì)應(yīng)元素。如果兩個(gè)元素不相等,那么兩個(gè)容器不匹配,函數(shù)返回 false
。
如果循環(huán)體結(jié)束后未發(fā)現(xiàn)任何不匹配的情況,表明兩個(gè)容器匹配,函數(shù)返回 true
。
下面演示了 allItemsMatch(_:_:)
函數(shù)的使用:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印 “All items match.”
上面的例子創(chuàng)建了一個(gè) Stack
實(shí)例來(lái)存儲(chǔ)一些 String
值,然后將三個(gè)字符串壓入棧中。這個(gè)例子還通過(guò)數(shù)組字面量創(chuàng)建了一個(gè) Array
實(shí)例,數(shù)組中包含同棧中一樣的三個(gè)字符串。即使棧和數(shù)組是不同的類(lèi)型,但它們都遵從 Container
協(xié)議,而且它們都包含相同類(lèi)型的值。因此你可以用這兩個(gè)容器作為參數(shù)來(lái)調(diào)用 allItemsMatch(_:_:)
函數(shù)。在上面的例子中,allItemsMatch(_:_:)
函數(shù)正確地顯示了這兩個(gè)容器中的所有元素都是相互匹配的。
你也可以使用泛型 where
子句作為擴(kuò)展的一部分?;谝郧暗睦樱旅娴氖纠龜U(kuò)展了泛型 Stack
結(jié)構(gòu)體,添加一個(gè) isTop(_:)
方法。
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
這個(gè)新的 isTop(_:)
方法首先檢查這個(gè)棧是不是空的,然后比較給定的元素與棧頂部的元素。如果你嘗試不用泛型 where
子句,會(huì)有一個(gè)問(wèn)題:在 isTop(_:)
里面使用了 ==
運(yùn)算符,但是 Stack
的定義沒(méi)有要求它的元素是符合 Equatable 協(xié)議的,所以使用 ==
運(yùn)算符導(dǎo)致編譯時(shí)錯(cuò)誤。使用泛型 where
子句可以為擴(kuò)展添加新的條件,因此只有當(dāng)棧中的元素符合 Equatable 協(xié)議時(shí),擴(kuò)展才會(huì)添加 isTop(_:)
方法。
以下是 isTop(_:)
方法的調(diào)用方式:
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// 打印 "Top element is tres."
如果嘗試在其元素不符合 Equatable 協(xié)議的棧上調(diào)用 isTop(_:)
方法,則會(huì)收到編譯時(shí)錯(cuò)誤。
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // 報(bào)錯(cuò)
你可以使用泛型 where
子句去擴(kuò)展一個(gè)協(xié)議。基于以前的示例,下面的示例擴(kuò)展了 Container
協(xié)議,添加一個(gè) startsWith(_:)
方法。
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
這個(gè) startsWith(_:)
方法首先確保容器至少有一個(gè)元素,然后檢查容器中的第一個(gè)元素是否與給定的元素相等。任何符合 Container
協(xié)議的類(lèi)型都可以使用這個(gè)新的 startsWith(_:)
方法,包括上面使用的棧和數(shù)組,只要容器的元素是符合 Equatable 協(xié)議的。
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// 打印 "Starts with something else."
上述示例中的泛型 where
子句要求 Item
符合協(xié)議,但也可以編寫(xiě)一個(gè)泛型 where
子句去要求 Item
為特定類(lèi)型。例如:
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印 "648.9"
此示例將一個(gè) average()
方法添加到 Item
類(lèi)型為 Double
的容器中。此方法遍歷容器中的元素將其累加,并除以容器的數(shù)量計(jì)算平均值。它將數(shù)量從 Int
轉(zhuǎn)換為 Double
確保能夠進(jìn)行浮點(diǎn)除法。
就像可以在其他地方寫(xiě)泛型 where
子句一樣,你可以在一個(gè)泛型 where
子句中包含多個(gè)條件作為擴(kuò)展的一部分。用逗號(hào)分隔列表中的每個(gè)條件。
你可以在關(guān)聯(lián)類(lèi)型后面加上具有泛型 where
的字句。例如,建立一個(gè)包含迭代器(Iterator)的容器,就像是標(biāo)準(zhǔn)庫(kù)中使用的 Sequence
協(xié)議那樣。你應(yīng)該這么寫(xiě):
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
迭代器(Iterator)的泛型 where
子句要求:無(wú)論迭代器是什么類(lèi)型,迭代器中的元素類(lèi)型,必須和容器項(xiàng)目的類(lèi)型保持一致。makeIterator()
則提供了容器的迭代器的訪問(wèn)接口。
一個(gè)協(xié)議繼承了另一個(gè)協(xié)議,你通過(guò)在協(xié)議聲明的時(shí)候,包含泛型 where
子句,來(lái)添加了一個(gè)約束到被繼承協(xié)議的關(guān)聯(lián)類(lèi)型。例如,下面的代碼聲明了一個(gè) ComparableContainer
協(xié)議,它要求所有的 Item
必須是 Comparable
的。
protocol ComparableContainer: Container where Item: Comparable { }
下標(biāo)能夠是泛型的,他們能夠包含泛型 where
子句。你可以把占位符類(lèi)型的名稱寫(xiě)在 subscript
后面的尖括號(hào)里,在下標(biāo)代碼體開(kāi)始的標(biāo)志的花括號(hào)之前寫(xiě)下泛型 where
子句。例如:
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
這個(gè) Container
協(xié)議的擴(kuò)展添加了一個(gè)下標(biāo)方法,接收一個(gè)索引的集合,返回每一個(gè)索引所在的值的數(shù)組。這個(gè)泛型下標(biāo)的約束如下:
這個(gè) Container
協(xié)議的擴(kuò)展添加了一個(gè)下標(biāo):下標(biāo)是一個(gè)序列的索引,返回的則是索引所在的項(xiàng)目的值所構(gòu)成的數(shù)組。這個(gè)泛型下標(biāo)的約束如下:
Indices
,必須是符合標(biāo)準(zhǔn)庫(kù)中的 Sequence
協(xié)議的類(lèi)型。indices
,必須是 Indices
的實(shí)例。where
子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int
類(lèi)型。這樣就能確保在序列(Sequence)中的索引和容器(Container)里面的索引類(lèi)型是一致的。綜合一下,這些約束意味著,傳入到 indices
下標(biāo),是一個(gè)整型的序列. (譯者:原來(lái)的 Container
協(xié)議,subscript
必須是 Int
型的,擴(kuò)展中新的 subscript
,允許下標(biāo)是一個(gè)的序列,而非單一的值。)