泛型代碼使您能夠編寫靈活的、可重用的函數(shù)和類型,這些函數(shù)和類型可以使用任何類型,取決于您定義的需求。您可以編寫避免重復(fù)的代碼,并以清晰、抽象的方式表達(dá)其意圖。
泛型是Swift最強(qiáng)大的特性之一,而且大部分Swift標(biāo)準(zhǔn)庫都是用泛型代碼構(gòu)建的。事實(shí)上,您在整個(gè)語言指南中都使用了泛型,即使您沒有意識(shí)到這一點(diǎn)。例如,Swift的數(shù)組和字典類型都是泛型集合。您可以創(chuàng)建一個(gè)包含Int值的數(shù)組,或者一個(gè)包含String值的數(shù)組,或者一個(gè)可以用Swift創(chuàng)建的任何其他類型的數(shù)組。類似地,您可以創(chuàng)建一個(gè)字典來存儲(chǔ)任何指定類型的值,并且對(duì)于該類型可以是什么沒有限制。
泛型解決的問題
下面是一個(gè)標(biāo)準(zhǔn)的沒有泛型的函數(shù)
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
這個(gè)函數(shù)是,交換兩個(gè)Int型數(shù)據(jù),那么如果是其他類型的,我們要寫相同交換方法 n 個(gè)。累死了?。。?/p>
編寫一個(gè)可以交換任意類型的兩個(gè)值的函數(shù)更有用,也更靈活。泛型代碼使您能夠編寫這樣的函數(shù)。(這些函數(shù)的通用版本定義如下。)
泛型函數(shù)
下面是一個(gè)標(biāo)準(zhǔn)的泛型函數(shù) swapTwoValues(::)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
現(xiàn)在可以使用這個(gè)泛型函數(shù)交換兩個(gè)字符串的值了。
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
上面定義的swapTwoValues(::)函數(shù)的靈感來自于一個(gè)名為swap的通用函數(shù),它是Swift標(biāo)準(zhǔn)庫的一部分,可以在應(yīng)用程序中自動(dòng)使用。如果需要在自己的代碼中使用swapTwoValues(::)函數(shù)的行為,可以使用Swift現(xiàn)有的swap(::)函數(shù),而不是提供自己的實(shí)現(xiàn)。
在上面的swapTwoValues(::)示例中,占位符類型T是類型參數(shù)的一個(gè)示例。類型參數(shù)指定并命名占位符類型,并在函數(shù)名稱之后立即在一對(duì)匹配的尖括號(hào)(如)之間寫入。
一旦指定了類型參數(shù),就可以使用它定義函數(shù)參數(shù)的類型(例如swapTwoValues(::)函數(shù)的a和b參數(shù)),或者作為函數(shù)的返回類型,或者作為函數(shù)體中的類型注釋。在每種情況下,每當(dāng)調(diào)用函數(shù)時(shí),類型參數(shù)都會(huì)替換為實(shí)際類型。(在上面的swapTwoValues(::)示例中,第一次調(diào)用函數(shù)時(shí)用Int替換T,第二次調(diào)用時(shí)用String替換T)。
您可以通過在尖括號(hào)中編寫多個(gè)類型參數(shù)名(以逗號(hào)分隔)來提供多個(gè)類型參數(shù)。
泛型
swift 允許你自定義泛型類型,可以是任何類型。
本節(jié)向您展示如何編寫名為Stack的泛型集合類型。堆棧是一組有序的值,類似于數(shù)組,但其操作集比Swift的數(shù)組類型更受限制。數(shù)組允許在數(shù)組中的任何位置插入和刪除新項(xiàng)。然而,堆棧只允許將新項(xiàng)附加到集合的末尾(稱為將新值推入堆棧)。類似地,堆棧只允許從集合的末尾刪除項(xiàng)(稱為從堆棧中彈出值)。
UINavigationController類使用堆棧的概念來為其導(dǎo)航層次結(jié)構(gòu)中的視圖控制器建模。您可以調(diào)用UINavigationController類pushViewController(:animated:)方法將視圖控制器添加(或推送)到導(dǎo)航堆棧上,并調(diào)用它的popViewControllerAnimated(:)方法從導(dǎo)航堆棧中刪除(或彈出)視圖控制器。當(dāng)您需要嚴(yán)格的“后進(jìn)先出”方法來管理集合時(shí),堆棧是一個(gè)有用的集合模型。
下圖顯示了棧的push和pop行為:

-
1 當(dāng)前堆棧上有三個(gè)值。
-
2 第四個(gè)值被推到堆棧的頂部。
-
3 堆?,F(xiàn)在包含四個(gè)值,最近的值位于頂部。
-
4 彈出堆棧中的頂部項(xiàng)。
-
5 彈出一個(gè)值后,堆棧再次包含三個(gè)值。
這里是如何編寫堆棧的非通用版本,在這種情況下,堆棧的Int值:
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
該結(jié)構(gòu)使用名為items的數(shù)組屬性在堆棧中存儲(chǔ)值。Stack提供了兩個(gè)方法push和pop,用于在棧上和棧外推送和彈出值。這些方法被標(biāo)記為mutating,因?yàn)樗鼈冃枰薷?或修改)結(jié)構(gòu)的items數(shù)組。
但是,上面顯示的IntStack類型只能與Int值一起使用。定義一個(gè)通用堆棧類會(huì)更有用,它可以管理任何類型值的堆棧。
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
注意,Stack的泛型版本與非泛型版本本質(zhì)上是相同的,但是類型參數(shù)是Element,而不是Int的實(shí)際類型。
元素為稍后提供的類型定義占位符名稱。這種未來類型可以在結(jié)構(gòu)定義的任何位置作為元素引用。在本例中,元素被用作三個(gè)位置的占位符:
- 要?jiǎng)?chuàng)建一個(gè)名為items的屬性,該屬性使用類型元素的值的空數(shù)組初始化
- 要指定push(_:)方法有一個(gè)名為item的參數(shù),該參數(shù)必須Element類型
- 指定pop()方法返回的值為Elementl類型的值
因?yàn)樗且粋€(gè)泛型類型,所以可以使用Stack在Swift中創(chuàng)建任何有效類型的堆棧,方法類似于Array和Dictionary。
你可以通過編寫要存儲(chǔ)在尖括號(hào)中的類型,可以創(chuàng)建一個(gè)新的堆棧實(shí)例。例如,要?jiǎng)?chuàng)建一個(gè)新的字符串堆棧,可以編寫堆棧():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
下面是stackofstring將這四個(gè)值放入堆棧后的樣子:

彈出一個(gè)值從堆棧刪除并返回頂部的值,“cuatro”:
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
然后結(jié)果如下:

擴(kuò)展泛型類型
當(dāng)擴(kuò)展泛型類型時(shí),不提供類型參數(shù)列表作為擴(kuò)展定義的一部分。相反,擴(kuò)展體中可以使用來自原始類型定義的類型參數(shù)列表,并且原始類型參數(shù)名稱用于引用來自原始定義的類型參數(shù)。
下面的例子擴(kuò)展了通用堆棧類型,添加了一個(gè)只讀的計(jì)算屬性topItem,它返回堆棧上的頂項(xiàng),而不從堆棧中彈出它:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
topItem屬性返回type Element的可選值。如果堆棧為空,topItem返回nil;如果堆棧不是空的,topItem返回items數(shù)組中的最后一個(gè)項(xiàng)目。
注意,這個(gè)擴(kuò)展沒有定義類型參數(shù)列表。相反,在擴(kuò)展中使用堆棧類型的現(xiàn)有類型參數(shù)名稱Element來指示計(jì)算屬性topItem的可選類型。
計(jì)算屬性topItem現(xiàn)在可以與任何堆棧實(shí)例一起使用,在不刪除它的情況下訪問和查詢它的棧頂項(xiàng)。
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
泛型類型的擴(kuò)展還可以包括擴(kuò)展類型的實(shí)例必須滿足的需求,以便獲得新功能,如下面的泛型Where子句的擴(kuò)展中所述。
類型約束
swapTwoValues(::)函數(shù)和堆棧類型可以使用任何類型。然而,有時(shí)對(duì)可與泛型函數(shù)和泛型類型一起使用的類型強(qiáng)制某些類型約束是有用的。類型約束指定類型參數(shù)必須繼承自特定類,或符合特定協(xié)議或協(xié)議組合。
例如,Swift的字典類型對(duì)可以用作字典鍵的類型設(shè)置了限制。正如字典中所描述的,字典的鍵的類型必須是可替換的。也就是說,它必須提供一種方法使自己具有獨(dú)特的代表性。Dictionary需要它的鍵是可hashable的,這樣它就可以檢查它是否已經(jīng)包含了特定鍵的值。如果沒有這個(gè)要求,Dictionary就不能告訴它是否應(yīng)該插入或替換特定鍵的值,也不能找到字典中已經(jīng)存在的給定鍵的值。
這一要求是通過對(duì)Dictionary鍵類型的類型約束來實(shí)現(xiàn)的,它指定鍵類型必須符合Hashable協(xié)議,這是Swift標(biāo)準(zhǔn)庫中定義的一種特殊協(xié)議。所有Swift的基本類型(如String、Int、Double和Bool)在默認(rèn)情況下都是可hashable的。
在創(chuàng)建自定義泛型類型時(shí),可以定義自己的類型約束,這些約束提供了泛型編程的大部分功能。像Hashable這樣的抽象概念根據(jù)它們的概念特征來描述類型,而不是它們的具體類型。
類型約束語法
通過在類型參數(shù)的名稱后面放置一個(gè)類或協(xié)議約束(以冒號(hào)分隔)作為類型參數(shù)列表的一部分來編寫類型約束。泛型函數(shù)的類型約束的基本語法如下所示(盡管泛型函數(shù)的語法是相同的):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
上面的假設(shè)函數(shù)有兩個(gè)類型參數(shù)。第一個(gè)類型參數(shù)T有一個(gè)類型約束,要求T是某個(gè)類的子類。第二個(gè)類型參數(shù)U有一個(gè)類型約束,要求U遵守協(xié)議SomeProtocol。
類型約束作用
這是一個(gè)名為findIndex(ofString:in:)的非泛型函數(shù),它被賦予一個(gè)要查找的字符串值和一個(gè)字符串值數(shù)組,可以在其中找到它。函數(shù)的作用是:返回一個(gè)可選的Int值,如果找到了數(shù)組中第一個(gè)匹配的字符串,它將作為索引;
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
調(diào)用
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
但是,在數(shù)組中查找值索引的原則并不只適用于字符串。您可以編寫與泛型函數(shù)相同的功能,方法是用某種類型的T值替換任何提到的字符串。
下面是如何編寫findIndex(ofString:in:)的通用版本,稱為findIndex(of:in:)。注意,這個(gè)函數(shù)的返回類型仍然是Int?,因?yàn)楹瘮?shù)返回一個(gè)可選索引號(hào),而不是數(shù)組中的一個(gè)可選值。但是要注意,這個(gè)函數(shù)不能編譯,原因在例子之后解釋:
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
上面寫的這個(gè)函數(shù)不能編譯。問題在于相等性檢查,“if value == valueToFind”。并不是Swift中的所有類型都可以與等號(hào)操作符(==)進(jìn)行比較。例如,如果您創(chuàng)建了自己的類或結(jié)構(gòu)來表示一個(gè)復(fù)雜的數(shù)據(jù)模型,那么對(duì)于該類或結(jié)構(gòu)來說,“等于”的含義是Swift無法為您猜到的。因此,不可能保證此代碼適用于所有可能的類型T,并且在嘗試編譯代碼時(shí)報(bào)告了一個(gè)適當(dāng)?shù)腻e(cuò)誤。
然而,并非一切都已失去。Swift標(biāo)準(zhǔn)庫定義了一個(gè)名為Equatable的協(xié)議,它要求任何符合條件的類型實(shí)現(xiàn)等號(hào)操作符(==)和not等號(hào)操作符(!=)來比較該類型的任意兩個(gè)值。Swift的所有標(biāo)準(zhǔn)類型都自動(dòng)支持Equatable協(xié)議。
任何遵循Equatable協(xié)議的類型都可以安全地與findIndex(of:in:)函數(shù)一起使用,因?yàn)樗WC支持equal to操作符。為了表達(dá)這個(gè)事實(shí),當(dāng)你定義函數(shù)時(shí),你寫一個(gè)Equatable類型約束作為類型參數(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
}
現(xiàn)在編譯成功,可以用于任何類型的等式,如Double或String:
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
關(guān)聯(lián)類型 Associated Types
在定義協(xié)議時(shí),聲明一個(gè)或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分有時(shí)很有用。關(guān)聯(lián)類型為用作協(xié)議一部分的類型提供占位符名稱。在采用協(xié)議之前,不會(huì)指定用于關(guān)聯(lián)類型的實(shí)際類型。關(guān)聯(lián)類型使用associatedtype關(guān)鍵字指定。
下面是一個(gè)名為Container的協(xié)議示例,它聲明了一個(gè)名為Item的關(guān)聯(lián)類型:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container協(xié)議定義了任何容器都必須提供的三個(gè)必需功能:
- 必須能夠使用append(_:)方法向容器添加新項(xiàng)。
- 必須能夠通過返回Int值的count屬性訪問容器中項(xiàng)的計(jì)數(shù)。
- 必須能夠使用下標(biāo)檢索容器中的每一項(xiàng),下標(biāo)接受Int索引值。
該協(xié)議沒有指定容器中的項(xiàng)應(yīng)該如何存儲(chǔ)或允許存儲(chǔ)什么類型的項(xiàng)。協(xié)議只指定任何類型必須提供的三個(gè)功能位,以便將其視為容器。符合標(biāo)準(zhǔn)的類型可以提供額外的功能,只要它滿足這三個(gè)需求。
任何符合容器協(xié)議的類型都必須能夠指定它存儲(chǔ)的值的類型。具體來說,它必須確保只有正確類型的項(xiàng)被添加到容器中,并且必須清楚下標(biāo)返回的項(xiàng)的類型。
要定義這些需求,容器協(xié)議需要一種方法來引用容器將包含的元素的類型,而不需要知道特定容器的類型是什么。容器協(xié)議需要指定傳遞給append(_:)方法的任何值必須具有與容器元素類型相同的類型,并且容器下標(biāo)返回的值將具有與容器元素類型相同的類型。
為了實(shí)現(xiàn)這一點(diǎn),容器協(xié)議聲明了一個(gè)名為Item的關(guān)聯(lián)類型,它被寫成associatedtype Item。協(xié)議沒有定義什么項(xiàng)——任何符合要求的類型都可以提供這些信息。盡管如此,項(xiàng)目別名提供了一種方法來引用容器中項(xiàng)目的類型,并定義一個(gè)用于append(_:)方法和下標(biāo)的類型,以確保強(qiáng)制執(zhí)行任何容器的預(yù)期行為。
下面是來自上面泛型類型的非泛型IntStack類型的一個(gè)版本,適用于符合容器協(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類型實(shí)現(xiàn)了容器協(xié)議的所有三個(gè)需求,并且在每種情況下都封裝了IntStack類型現(xiàn)有功能的一部分來滿足這些需求。
此外,IntStack指定對(duì)于容器的這個(gè)實(shí)現(xiàn),使用的合適的項(xiàng)是Int類型,typealias Item = Int的定義將抽象的項(xiàng)類型轉(zhuǎn)換為容器協(xié)議的這個(gè)實(shí)現(xiàn)的具體的Int類型。
由于Swift的類型推斷,實(shí)際上不需要將Int的具體項(xiàng)聲明為IntStack定義的一部分。由于IntStack符合容器協(xié)議的所有要求,Swift只需查看append(_:)方法的Item參數(shù)的類型和下標(biāo)的返回類型,就可以推斷出要使用的適當(dāng)項(xiàng)。實(shí)際上,如果您從上面的代碼中刪除typealias Item = Int行,那么一切仍然可以工作,因?yàn)閷?duì)于Item應(yīng)該使用什么類型是很清楚的。
你也可以使用泛型類型遵循該Container協(xié)議
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]
}
}
這一次,類型參數(shù)元素用作append(_:)方法的項(xiàng)參數(shù)的類型和下標(biāo)的返回類型。因此,Swift可以推斷元素是作為該特定容器項(xiàng)使用的適當(dāng)類型。
擴(kuò)展現(xiàn)有類型以指定關(guān)聯(lián)類型
您可以擴(kuò)展現(xiàn)有類型以向協(xié)議添加一致性,如使用擴(kuò)展添加協(xié)議一致性中所述。這包括具有關(guān)聯(lián)類型的協(xié)議。
Swift的數(shù)組類型已經(jīng)提供了一個(gè)append(_:)方法、一個(gè)count屬性和一個(gè)下標(biāo),下標(biāo)帶有一個(gè)Int索引來檢索它的元素。這三個(gè)功能符合容器協(xié)議的要求。這意味著只需聲明Array采用該協(xié)議,就可以擴(kuò)展Array以符合容器協(xié)議。您可以使用一個(gè)空擴(kuò)展來實(shí)現(xiàn)這一點(diǎn),正如在聲明使用擴(kuò)展采用協(xié)議時(shí)所述:
Array現(xiàn)有的append(_:)方法和下標(biāo)使Swift能夠推斷出要為Item使用的適當(dāng)類型,就像上面的通用堆棧類型一樣。定義此擴(kuò)展之后,可以使用任何數(shù)組作為容器。
給關(guān)聯(lián)類型添加類型約束
您可以向協(xié)議中的關(guān)聯(lián)類型添加類型約束,以要求符合標(biāo)準(zhǔn)的類型滿足這些約束。例如,下面的代碼定義了容器的一個(gè)版本,該版本要求容器中的項(xiàng)是相等的。
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
為了符合這個(gè)版本的容器,容器的項(xiàng)類型必須符合Equatable協(xié)議。
在關(guān)聯(lián)類型的約束中使用協(xié)議
協(xié)議可以作為其自身需求的一部分出現(xiàn)。例如,這里有一個(gè)協(xié)議改進(jìn)了容器協(xié)議,添加了suffix(:)方法的要求。suffix(:)方法從容器的末尾返回給定數(shù)量的元素,并將它們存儲(chǔ)在Suffix類型的實(shí)例中。
在這個(gè)協(xié)議中,Suffix 是一個(gè)關(guān)聯(lián)類型,就像上面容器示例中的項(xiàng)類型一樣。Suffix 有兩個(gè)約束:它必須符合Suffixablecontainer協(xié)議(目前正在定義的協(xié)議),并且它的項(xiàng)類型必須與容器的項(xiàng)類型相同。Item上的約束是一個(gè)泛型where子句,將在下面的泛型where子句的關(guān)聯(lián)類型中討論。
下面是上面泛型類型的堆棧類型擴(kuò)展,它增加了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
}
// 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的后綴關(guān)聯(lián)類型也是Stack,因此Stack上的后綴操作返回另一個(gè)堆棧。另外,符合SuffixableContainer的類型可以具有不同于自身的后綴類型——這意味著后綴操作可以返回不同的類型。例如,這里有一個(gè)非泛型IntStack類型的擴(kuò)展,添加了SuffixableContainer一致性,使用Stack作為后綴類型,而不是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
}
// Inferred that Suffix is Stack<Int>.
}
泛型 Where 語句
類型約束(如類型約束中所述)使您能夠?qū)εc泛型函數(shù)、下標(biāo)或類型關(guān)聯(lián)的類型參數(shù)定義需求。
為關(guān)聯(lián)類型定義需求也很有用。您可以通過定義一個(gè)通用where子句來實(shí)現(xiàn)這一點(diǎn)。泛型where子句允許您要求關(guān)聯(lián)類型必須符合某個(gè)協(xié)議,或者某些類型參數(shù)和關(guān)聯(lián)類型必須相同。泛型where子句以where關(guān)鍵字開始,然后是關(guān)聯(lián)類型的約束或類型與關(guān)聯(lián)類型之間的相等關(guān)系。在類型或函數(shù)主體的左大括號(hào)前編寫一個(gè)泛型where子句。
下面的示例定義了一個(gè)名為allItemsMatch的泛型函數(shù),該函數(shù)檢查兩個(gè)容器實(shí)例是否以相同的順序包含相同的項(xiàng)。如果所有項(xiàng)匹配,函數(shù)返回一個(gè)布爾值true;如果不匹配,返回一個(gè)值false。
要檢查的兩個(gè)容器不必是相同類型的容器(雖然可以),但是它們必須包含相同類型的項(xiàng)目。這一要求是通過類型約束和通用where子句的組合來表示的:
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
這個(gè)函數(shù)接受兩個(gè)參數(shù)someContainer和anotherContainer。someContainer參數(shù)類型為C1,anotherContainer參數(shù)類型為C2。C1和C2都是調(diào)用函數(shù)時(shí)要確定的兩種容器類型的類型參數(shù)。
以下是對(duì)函數(shù)的兩個(gè)類型參數(shù)的要求:
- C1必須符合容器協(xié)議(寫為C1: Container)。
- C2還必須符合容器協(xié)議(寫為C2: Container)。
- C1的項(xiàng)必須與C2的項(xiàng)相同(C1.Item == C2.Item)。
- C1項(xiàng)必須符合Equatable協(xié)議(C1.Item: Equatable)。
第一個(gè)和第二個(gè)需求在函數(shù)的類型參數(shù)列表中定義,第三和第四個(gè)需求在函數(shù)的泛型where子句中定義。
使用函數(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.")
}
// Prints "All items match."
上面的示例創(chuàng)建一個(gè)堆棧實(shí)例來存儲(chǔ)字符串值,并將三個(gè)字符串推入堆棧。該示例還創(chuàng)建一個(gè)數(shù)組實(shí)例,初始化后的數(shù)組文本包含與堆棧相同的三個(gè)字符串。盡管堆棧和數(shù)組屬于不同的類型,但它們都符合容器協(xié)議,并且都包含相同類型的值。因此,可以使用這兩個(gè)容器作為參數(shù)調(diào)用allItemsMatch(::)函數(shù)。在上面的例子中,allItemsMatch(::)函數(shù)正確地報(bào)告兩個(gè)容器中的所有項(xiàng)都匹配。
泛型Where 語句的擴(kuò)展
還可以使用通用where子句作為擴(kuò)展的一部分。下面的示例擴(kuò)展了前面示例中的通用堆棧結(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(:)方法首先檢查堆棧是否為空,然后將給定的項(xiàng)與堆棧的最上面的項(xiàng)進(jìn)行比較。如果您嘗試在沒有通用where子句的情況下執(zhí)行此操作,那么就會(huì)遇到一個(gè)問題:isTop(:)的實(shí)現(xiàn)使用==操作符,但是堆棧的定義并不要求它的項(xiàng)是相等的,因此使用==操作符會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤。使用泛型where子句可以向擴(kuò)展添加新需求,因此只有當(dāng)堆棧中的項(xiàng)相等時(shí),擴(kuò)展才會(huì)添加isTop(_:)方法。
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
如果嘗試在元素不相等的堆棧上調(diào)用isTop(_:)方法,將會(huì)得到編譯時(shí)錯(cuò)誤。
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
可以使用帶協(xié)議擴(kuò)展的通用where子句。下面的示例擴(kuò)展了前面示例中的容器協(xié)議,添加了一個(gè)startsWith(_:)方法。
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
startsWith(:)方法首先確保容器至少有一個(gè)項(xiàng),然后檢查容器中的第一個(gè)項(xiàng)是否與給定的項(xiàng)匹配。這個(gè)新的startsWith(:)方法可以用于任何符合容器協(xié)議的類型,包括上面使用的堆棧和數(shù)組,只要容器的項(xiàng)是相等的。
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
上面示例中的泛型where子句要求Item符合協(xié)議,但是也可以編寫泛型where子句,要求Item是特定類型。例如:
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())
// Prints "648.9"
這個(gè)例子向項(xiàng)類型為Double的容器添加了一個(gè)average()方法。它遍歷容器中的項(xiàng)并將它們相加,然后除以容器的計(jì)數(shù)來計(jì)算平均值。它顯式地將計(jì)數(shù)從Int轉(zhuǎn)換為Double,以便能夠進(jìn)行浮點(diǎn)除法。
可以在擴(kuò)展的泛型where子句中包含多個(gè)需求,就像可以在其他地方編寫泛型where子句一樣。用逗號(hào)分隔列表中的每個(gè)需求。
與泛型Where子句關(guān)聯(lián)的類型
可以在關(guān)聯(lián)類型上包含泛型where子句。例如,假設(shè)您想要?jiǎng)?chuàng)建一個(gè)包含迭代器的容器版本,就像序列協(xié)議在標(biāo)準(zhǔn)庫中使用的那樣。你可以這樣寫:
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
}
迭代器上的泛型where子句要求迭代器必須遍歷與容器項(xiàng)類型相同的項(xiàng)類型的元素,而不管迭代器的類型如何。函數(shù)的作用是:提供對(duì)容器迭代器的訪問。
對(duì)于從另一個(gè)協(xié)議繼承的協(xié)議,通過在協(xié)議聲明中包含泛型where子句,可以向繼承的關(guān)聯(lián)類型添加約束。例如,下面的代碼聲明了一個(gè)ComparableContainer協(xié)議,該協(xié)議要求項(xiàng)目符合Comparable:
protocol ComparableContainer: Container where Item: Comparable { }
泛型下標(biāo) Subscripts
下標(biāo)可以是泛型的,它們可以包含泛型where子句。您可以在下標(biāo)后的尖括號(hào)內(nèi)編寫占位符類型名稱,并在下標(biāo)主體的左大括號(hào)前編寫一個(gè)泛型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
}
}
容器協(xié)議的這個(gè)擴(kuò)展添加了一個(gè)下標(biāo),它接受一系列索引,并返回一個(gè)數(shù)組,其中包含每個(gè)給定索引上的項(xiàng)。這個(gè)通用下標(biāo)的約束條件如下:
- 尖括號(hào)中的泛型參數(shù)索引必須是符合標(biāo)準(zhǔn)庫中的序列協(xié)議的類型。
- 下標(biāo)接受單個(gè)參數(shù)indexes,這是該索引類型的一個(gè)實(shí)例。
- 通用where子句要求序列的迭代器必須遍歷Int類型的元素,這確保序列中的索引與容器中使用的索引類型相同。
總的來說,這些約束意味著為索引參數(shù)傳遞的值是一個(gè)整數(shù)序列。