在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 泛型
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問(wèn)控制和 protected
語(yǔ)句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語(yǔ)言參考(About the Language Reference)
語(yǔ)法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(jiàn)(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過(guò)程
關(guān)于 Swift(About Swift)
訪問(wèn)控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語(yǔ)言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問(wèn)題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過(guò)程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

泛型


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-13

3.1:翻譯:qhd,2017-04-10

4.0 翻譯+校對(duì):kemchenj 2017-09-21

4.1 翻譯+校對(duì):mylittleswift

本頁(yè)包含內(nèi)容:

泛型代碼讓你能夠根據(jù)自定義的需求,編寫(xiě)出適用于任意類型、靈活可重用的函數(shù)及類型。它能讓你避免代碼的重復(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 的 ArrayDictionary 都是泛型集合。你可以創(chuàng)建一個(gè) Int 數(shù)組,也可創(chuàng)建一個(gè) String 數(shù)組,甚至可以是任意其他 Swift 類型的數(shù)組。同樣的,你也可以創(chuàng)建存儲(chǔ)任意指定類型的字典。

泛型所解決的問(wèn)題

下面是一個(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)交換 ab 的值,請(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ù)功能都是相同的,唯一不同之處就在于傳入的變量類型不同,分別是 Int、StringDouble。

在實(shí)際應(yīng)用中,通常需要一個(gè)更實(shí)用更靈活的函數(shù)來(lái)交換兩個(gè)任意類型的值,幸運(yùn)的是,泛型代碼幫你解決了這種問(wèn)題。(這些函數(shù)的泛型版本已經(jīng)在下面定義好了。)

注意

在上面三個(gè)函數(shù)中,ab 類型必須相同。如果 ab 類型不同,那它們倆就不能互換值。Swift 是類型安全的語(yǔ)言,所以它不允許一個(gè) String 類型的變量和一個(gè) Double 類型的變量互換值。試圖這樣做將導(dǎo)致編譯錯(cuò)誤。

泛型函數(shù)

泛型函數(shù)可以適用于任何類型,下面的 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ù)的泛型版本使用了占位類型名(在這里用字母 T 來(lái)表示)來(lái)代替實(shí)際類型名(例如 Int、StringDouble)。占位類型名沒(méi)有指明 T 必須是什么類型,但是它指明了 ab 必須是同一類型 T,無(wú)論 T 代表什么類型。只有 swapTwoValues(_:_:) 函數(shù)在調(diào)用時(shí),才會(huì)根據(jù)所傳入的實(shí)際類型決定 T 所代表的類型。

泛型函數(shù)和非泛型函數(shù)的另外一個(gè)不同之處,在于這個(gè)泛型函數(shù)名(swapTwoValues(_:_:))后面跟著占位類型名(T),并用尖括號(hào)括起來(lái)(<T>)。這個(gè)尖括號(hào)告訴 Swift 那個(gè) TswapTwoValues(_:_:) 函數(shù)定義內(nèi)的一個(gè)占位類型名,因此 Swift 不會(huì)去查找名為 T 的實(shí)際類型。

swapTwoValues(_:_:) 函數(shù)現(xiàn)在可以像 swapTwoInts(_:_:) 那樣調(diào)用,不同的是它能接受兩個(gè)任意類型的值,條件是這兩個(gè)值有著相同的類型。swapTwoValues(_:_:) 函數(shù)被調(diào)用時(shí),T 所代表的類型都會(huì)由傳入的值的類型推斷出來(lái)。

在下面的兩個(gè)例子中,T 分別代表 IntString

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)用程序中使用它。如果你在代碼中需要類似 swapTwoValues(_:_:) 函數(shù)的功能,你可以使用已存在的 swap(_:_:) 函數(shù)。

類型參數(shù)

在上面的 swapTwoValues(_:_:) 例子中,占位類型 T 是類型參數(shù)的一個(gè)例子。類型參數(shù)指定并命名一個(gè)占位類型,并且緊隨在函數(shù)名后面,使用一對(duì)尖括號(hào)括起來(lái)(例如 <T>)。

一旦一個(gè)類型參數(shù)被指定,你可以用它來(lái)定義一個(gè)函數(shù)的參數(shù)類型(例如 swapTwoValues(_:_:) 函數(shù)中的參數(shù) ab),或者作為函數(shù)的返回類型,還可以用作函數(shù)主體中的注釋類型。在這些情況下,類型參數(shù)會(huì)在函數(shù)調(diào)用時(shí)被實(shí)際類型所替換。(在上面的 swapTwoValues(_:_:) 例子中,當(dāng)函數(shù)第一次被調(diào)用時(shí),TInt 替換,第二次調(diào)用時(shí),被 String 替換。)

你可提供多個(gè)類型參數(shù),將它們都寫(xiě)在尖括號(hào)中,用逗號(hào)分開(kāi)。

命名類型參數(shù)

在大多數(shù)情況下,類型參數(shù)具有一個(gè)描述性名字,例如 Dictionary<Key, Value> 中的 KeyValue,以及 Array<Element> 中的 Element,這可以告訴閱讀代碼的人這些類型參數(shù)和泛型函數(shù)之間的關(guān)系。然而,當(dāng)它們之間沒(méi)有有意義的關(guān)系時(shí),通常使用單個(gè)字母來(lái)命名,例如 T、UV,正如上面演示的 swapTwoValues(_:_:) 函數(shù)中的 T 一樣。

注意

請(qǐng)始終使用大寫(xiě)字母開(kāi)頭的駝峰命名法(例如 TMyTypeParameter)來(lái)為類型參數(shù)命名,以表明它們是占位類型,而不是一個(gè)值。

泛型類型

除了泛型函數(shù),Swift 還允許你定義泛型類型。這些自定義類、結(jié)構(gòu)體和枚舉可以適用于任何類型,類似于 ArrayDictionary。

這部分內(nèi)容將向你展示如何編寫(xiě)一個(gè)名為 Stack (棧)的泛型集合類型。棧是一系列值的有序集合,和 Array 類似,但它相比 Swift 的 Array 類型有更多的操作限制。數(shù)組允許在數(shù)組的任意位置插入新元素或是刪除其中任意位置的元素。而棧只允許在集合的末端添加新的元素(稱之為棧)。類似的,棧也只能從末端移除元素(稱之為棧)。

注意

棧的概念已被 UINavigationController 類用來(lái)構(gòu)造視圖控制器的導(dǎo)航結(jié)構(gòu)。你通過(guò)調(diào)用 UINavigationControllerpushViewController(_:animated:) 方法來(lái)添加新的視圖控制器到導(dǎo)航棧,通過(guò) popViewControllerAnimated(_:) 方法來(lái)從導(dǎo)航棧中移除視圖控制器。每當(dāng)你需要一個(gè)嚴(yán)格的“后進(jìn)先出”方式來(lái)管理集合,棧都是最實(shí)用的模型。

下圖展示了一個(gè)棧的入棧(push)和出棧(pop)的行為:

此處輸入圖片的描述

  1. 現(xiàn)在有三個(gè)值在棧中。
  2. 第四個(gè)值被壓入到棧的頂部。
  3. 現(xiàn)在有四個(gè)值在棧中,最近入棧的那個(gè)值在頂部。
  4. 棧中最頂部的那個(gè)值被移除出棧。
  5. 一個(gè)值移除出棧后,現(xiàn)在棧又只有三個(gè)值了。

下面展示了如何編寫(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è)名為 itemsArray 屬性來(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 類型。不過(guò),可以定義一個(gè)泛型 Stack 結(jié)構(gòu)體,從而能夠處理任意類型的值。

下面是相同代碼的泛型版本:

struct Stack<Element> {
    var items = [Element]()
 ? ?mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

注意,Stack 基本上和 IntStack 相同,只是用占位類型參數(shù) Element 代替了實(shí)際的 Int 類型。這個(gè)類型參數(shù)包裹在緊隨結(jié)構(gòu)體名的一對(duì)尖括號(hào)里(<Element>)。

Element 為待提供的類型定義了一個(gè)占位名。這種待提供的類型可以在結(jié)構(gòu)體的定義中通過(guò) Element 來(lái)引用。在這個(gè)例子中,Element 在如下三個(gè)地方被用作占位符:

  • 創(chuàng)建 items 屬性,使用 Element 類型的空數(shù)組對(duì)其進(jìn)行初始化。
  • 指定 push(_:) 方法的唯一參數(shù) item 的類型必須是 Element 類型。
  • 指定 pop() 方法的返回值類型必須是 Element 類型。

由于 Stack 是泛型類型,因此可以用來(lái)創(chuàng)建 Swift 中任意有效類型的棧,就像 ArrayDictionary 那樣。

你可以通過(guò)在尖括號(hào)中寫(xiě)出棧中需要存儲(chǔ)的數(shù)據(jù)類型來(lái)創(chuàng)建并初始化一個(gè) Stack 實(shí)例。例如,要?jiǎng)?chuàng)建一個(gè) String 類型的棧,可以寫(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 如何將頂部的值出棧:

此處輸入圖片的描述

擴(kuò)展一個(gè)泛型類型

當(dāng)你擴(kuò)展一個(gè)泛型類型的時(shí)候,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表。原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展中可以直接使用,并且這些來(lái)自原始類型中的參數(shù)名稱會(huì)被用作原始定義中類型參數(shù)的引用。

下面的例子擴(kuò)展了泛型類型 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 類型的可選值。當(dāng)棧為空的時(shí)候,topItem 會(huì)返回 nil;當(dāng)棧不為空的時(shí)候,topItem 會(huì)返回 items 數(shù)組中的最后一個(gè)元素。

注意,這個(gè)擴(kuò)展并沒(méi)有定義一個(gè)類型參數(shù)列表。相反的,Stack 類型已有的類型參數(shù)名稱 Element,被用在擴(kuò)展中來(lái)表示計(jì)算型屬性 topItem 的可選類型。

計(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 類型可以作用于任何類型。不過(guò),有的時(shí)候如果能將使用在泛型函數(shù)和泛型類型中的類型添加一個(gè)特定的類型約束,將會(huì)是非常有用的。類型約束可以指定一個(gè)類型參數(shù)必須繼承自指定類,或者符合一個(gè)特定的協(xié)議或協(xié)議組合。

例如,Swift 的 Dictionary 類型對(duì)字典的鍵的類型做了些限制。在字典的描述中,字典的鍵的類型必須是可哈希(hashable)的。也就是說(shuō),必須有一種方法能夠唯一地表示它。Dictionary 的鍵之所以要是可哈希的,是為了便于檢查字典是否已經(jīng)包含某個(gè)特定鍵的值。若沒(méi)有這個(gè)要求,Dictionary 將無(wú)法判斷是否可以插入或者替換某個(gè)指定鍵的值,也不能查找到已經(jīng)存儲(chǔ)在字典中的指定鍵的值。

為了實(shí)現(xiàn)這個(gè)要求,一個(gè)類型約束被強(qiáng)制加到 Dictionary 的鍵類型上,要求其鍵類型必須符合 Hashable 協(xié)議,這是 Swift 標(biāo)準(zhǔn)庫(kù)中定義的一個(gè)特定協(xié)議。所有的 Swift 基本類型(例如 StringInt、DoubleBool)默認(rèn)都是可哈希的。

當(dāng)你創(chuàng)建自定義泛型類型時(shí),你可以定義你自己的類型約束,這些約束將提供更為強(qiáng)大的泛型編程能力。抽象概念,例如可哈希的,描述的是類型在概念上的特征,而不是它們的顯式類型。

類型約束語(yǔ)法

你可以在一個(gè)類型參數(shù)名后面放置一個(gè)類名或者協(xié)議名,并用冒號(hào)進(jìn)行分隔,來(lái)定義類型約束,它們將成為類型參數(shù)列表的一部分。對(duì)泛型函數(shù)添加類型約束的基本語(yǔ)法如下所示(作用于泛型類型時(shí)的語(yǔ)法與之相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這里是泛型函數(shù)的函數(shù)體部分
}

上面這個(gè)函數(shù)有兩個(gè)類型參數(shù)。第一個(gè)類型參數(shù) T,有一個(gè)要求 T 必須是 SomeClass 子類的類型約束;第二個(gè)類型參數(shù) U,有一個(gè)要求 U 必須符合 SomeProtocol 協(xié)議的類型約束。

類型約束實(shí)踐

這里有個(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ò),你可以用占位類型 T 替換 String 類型來(lái)寫(xiě)出具有相同功能的泛型函數(shù) findIndex(_:_:)。

下面展示了 findIndex(ofString:in:) 函數(shù)的泛型版本 findIndex(ofString:in:)。請(qǐng)注意這個(gè)函數(shù)返回值的類型仍然是 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 類型都可以用等式符(==)進(jìn)行比較。比如說(shuō),如果你創(chuàng)建一個(gè)自定義的類或結(jié)構(gòu)體來(lái)表示一個(gè)復(fù)雜的數(shù)據(jù)模型,那么 Swift 無(wú)法猜到對(duì)于這個(gè)類或結(jié)構(gòu)體而言“相等”意味著什么。正因如此,這部分代碼無(wú)法保證適用于每個(gè)可能的類型 T,當(dāng)你試圖編譯這部分代碼時(shí)會(huì)出現(xiàn)相應(yīng)的錯(cuò)誤。

不過(guò),所有的這些并不會(huì)讓我們無(wú)從下手。Swift 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè) Equatable 協(xié)議,該協(xié)議要求任何遵循該協(xié)議的類型必須實(shí)現(xiàn)等式符(==)及不等符(!=),從而能對(duì)該類型的任意兩個(gè)值進(jìn)行比較。所有的 Swift 標(biāo)準(zhǔn)類型自動(dòng)支持 Equatable 協(xié)議。

任何 Equatable 類型都可以安全地使用在 findIndex(of:in:) 函數(shù)中,因?yàn)槠浔WC支持等式操作符。為了說(shuō)明這個(gè)事實(shí),當(dāng)你定義一個(gè)函數(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
}

findIndex(of:in:) 唯一的類型參數(shù)寫(xiě)做 T: Equatable,也就意味著“任何符合 Equatable 協(xié)議的類型 T”。

findIndex(of:in:) 函數(shù)現(xiàn)在可以成功編譯了,并且可以作用于任何符合 Equatable 的類型,如 DoubleString

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 類型為 Int?,其值為 nil,因?yàn)?9.3 不在數(shù)組中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 類型為 Int?,其值為 2

關(guān)聯(lián)類型

定義一個(gè)協(xié)議時(shí),有的時(shí)候聲明一個(gè)或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分將會(huì)非常有用。關(guān)聯(lián)類型為協(xié)議中的某個(gè)類型提供了一個(gè)占位名(或者說(shuō)別名),其代表的實(shí)際類型在協(xié)議被采納時(shí)才會(huì)被指定。你可以通過(guò) associatedtype 關(guān)鍵字來(lái)指定關(guān)聯(lián)類型。

關(guān)聯(lián)類型實(shí)踐

下面例子定義了一個(gè) Container 協(xié)議,該協(xié)議定義了一個(gè)關(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é)議定義了三個(gè)任何采納了該協(xié)議的類型(即容器)必須提供的功能:

  • 必須可以通過(guò) append(_:) 方法添加一個(gè)新元素到容器里。
  • 必須可以通過(guò) count 屬性獲取容器中元素的數(shù)量,并返回一個(gè) Int 值。
  • 必須可以通過(guò)索引值類型為 Int 的下標(biāo)檢索到容器中的每一個(gè)元素。

這個(gè)協(xié)議沒(méi)有指定容器中元素該如何存儲(chǔ),以及元素必須是何種類型。這個(gè)協(xié)議只指定了三個(gè)任何遵從 Container 協(xié)議的類型必須提供的功能。遵從協(xié)議的類型在滿足這三個(gè)條件的情況下也可以提供其他額外的功能。

任何遵從 Container 協(xié)議的類型必須能夠指定其存儲(chǔ)的元素的類型,必須保證只有正確類型的元素可以加進(jìn)容器中,必須明確通過(guò)其下標(biāo)返回的元素的類型。

為了定義這三個(gè)條件,Container 協(xié)議需要在不知道容器中元素的具體類型的情況下引用這種類型。Container 協(xié)議需要指定任何通過(guò) append(_:) 方法添加到容器中的元素和容器中的元素是相同類型,并且通過(guò)容器下標(biāo)返回的元素的類型也是這種類型。

為了達(dá)到這個(gè)目的,Container 協(xié)議聲明了一個(gè)關(guān)聯(lián)類型 Item,寫(xiě)作 associatedtype Item。這個(gè)協(xié)議無(wú)法定義 Item 是什么類型的別名,這個(gè)信息將留給遵從協(xié)議的類型來(lái)提供。盡管如此,Item 別名提供了一種方式來(lái)引用 Container 中元素的類型,并將之用于 append(_:) 方法和下標(biāo),從而保證任何 Container 的行為都能夠正如預(yù)期地被執(zhí)行。

下面是先前的非泛型的 IntStack 類型,這一版本采納并符合了 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 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)體實(shí)現(xiàn)了 Container 協(xié)議的三個(gè)要求,其原有功能也不會(huì)和這些要求相沖突。

此外,IntStack 在實(shí)現(xiàn) Container 的要求時(shí),指定 ItemInt 類型,即 typealias Item = Int,從而將 Container 協(xié)議中抽象的 Item 類型轉(zhuǎn)換為具體的 Int 類型。

由于 Swift 的類型推斷,你實(shí)際上不用在 IntStack 的定義中聲明 ItemInt。因?yàn)?IntStack 符合 Container 協(xié)議的所有要求,Swift 只需通過(guò) append(_:) 方法的 item 參數(shù)類型和下標(biāo)返回值的類型,就可以推斷出 Item 的具體類型。事實(shí)上,如果你在上面的代碼中刪除了 typealias Item = Int 這一行,一切仍舊可以正常工作,因?yàn)?Swift 清楚地知道 Item 應(yīng)該是哪種類型。

你也可以讓泛型 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]
    }
}

這一次,占位類型參數(shù) Element 被用作 append(_:) 方法的 item 參數(shù)和下標(biāo)的返回類型。Swift 可以據(jù)此推斷出 Element 的類型即是 Item 的類型。

通過(guò)擴(kuò)展一個(gè)存在的類型來(lái)指定關(guān)聯(lián)類型

通過(guò)擴(kuò)展添加協(xié)議一致性中描述了如何利用擴(kuò)展讓一個(gè)已存在的類型符合一個(gè)協(xié)議,這包括使用了關(guān)聯(lián)類型的協(xié)議。

Swift 的 Array 類型已經(jīng)提供 append(_:) 方法,一個(gè) count 屬性,以及一個(gè)接受 Int 類型索引值的下標(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)體一樣,Arrayappend(_:) 方法和下標(biāo)確保了 Swift 可以推斷出 Item 的類型。定義了這個(gè)擴(kuò)展后,你可以將任意 Array 當(dāng)作 Container 來(lái)使用。

給關(guān)聯(lián)類型添加約束

你可以給協(xié)議里的關(guān)聯(lián)類型添加類型注釋,讓遵守協(xié)議的類型必須遵循這個(gè)約束條件。例如,下面的代碼定義了一個(gè) Item 必須遵循 EquatableContainer 類型:

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)。例如,有一個(gè)協(xié)議細(xì)化了 Container 協(xié)議,添加了一個(gè) suffix(_:) 方法。suffix(_:) 方法返回容器中從后往前給定數(shù)量的元素,把它們存儲(chǔ)在一個(gè) Suffix 類型的實(shí)例里。

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

在這個(gè)協(xié)議里,Suffix 是一個(gè)關(guān)聯(lián)類型,就像上邊例子中 ContainerItem 類型一樣。Suffix 擁有兩個(gè)約束:它必須遵循 SuffixableContainer 協(xié)議(就是當(dāng)前定義的協(xié)議),以及它的 Item 類型必須是和容器里的 Item 類型相同。Item 的約束是一個(gè) where 分句,它在下面帶有泛型 Where 分句的擴(kuò)展中有討論。

這里有一個(gè)來(lái)自閉包的循環(huán)強(qiáng)引用的 Stack 類型的擴(kuò)展,它添加了對(duì) 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

在上面的例子中,SuffixStack 的關(guān)聯(lián)類型,也就是 Stack ,所以 Stack 的后綴運(yùn)算返回另一個(gè) Stack 。另外,遵循 SuffixableContainer 的類型可以擁有一個(gè)與它自己不同的 Suffix 類型——也就是說(shuō)后綴運(yùn)算可以返回不同的類型。比如說(shuō),這里有一個(gè)非泛型 IntStack 類型的擴(kuò)展,它添加了 SuffixableContainer 遵循,使用 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
    }
    // Inferred that Suffix is Stack<Int>.
}

泛型 Where 語(yǔ)句

類型約束讓你能夠?yàn)榉盒秃瘮?shù),下標(biāo),類型的類型參數(shù)定義一些強(qiáng)制要求。

為關(guān)聯(lián)類型定義約束也是非常有用的。你可以在參數(shù)列表中通過(guò) where 子句為關(guān)聯(lián)類型定義約束。你能通過(guò) where 子句要求一個(gè)關(guān)聯(lián)類型遵從某個(gè)特定的協(xié)議,以及某個(gè)特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同。你可以通過(guò)將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來(lái)定義 where 子句,where 子句后跟一個(gè)或者多個(gè)針對(duì)關(guān)聯(lián)類型的約束,以及一個(gè)或多個(gè)類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系。你可以在函數(shù)體或者類型的大括號(hào)之前添加 where 子句。

下面的例子定義了一個(gè)名為 allItemsMatch 的泛型函數(shù),用來(lái)檢查兩個(gè) Container 實(shí)例是否包含相同順序的相同元素。如果所有的元素能夠匹配,那么返回 true,否則返回 false。

被檢查的兩個(gè) Container 可以不是相同類型的容器(雖然它們可以相同),但它們必須擁有相同類型的元素。這個(gè)要求通過(guò)一個(gè)類型約束以及一個(gè) where 子句來(lái)表示:

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: 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ù)接受 someContaineranotherContainer 兩個(gè)參數(shù)。參數(shù) someContainer 的類型為 C1,參數(shù) anotherContainer 的類型為 C2。C1C2 是容器的兩個(gè)占位類型參數(shù),函數(shù)被調(diào)用時(shí)才能確定它們的具體類型。

這個(gè)函數(shù)的類型參數(shù)列表還定義了對(duì)兩個(gè)類型參數(shù)的要求:

  • C1 必須符合 Container 協(xié)議(寫(xiě)作 C1: Container)。
  • C2 必須符合 Container 協(xié)議(寫(xiě)作 C2: Container)。
  • C1Item 必須和 C2Item 類型相同(寫(xiě)作 C1.Item == C2.Item)。
  • C1Item 必須符合 Equatable 協(xié)議(寫(xiě)作 C1.Item: Equatable)。

第三個(gè)和第四個(gè)要求被定義為一個(gè) where 子句,寫(xiě)在關(guān)鍵字 where 后面,它們也是泛型函數(shù)類型參數(shù)列表的一部分。

這些要求意味著:

  • someContainer 是一個(gè) C1 類型的容器。
  • anotherContainer 是一個(gè) C2 類型的容器。
  • someContaineranotherContainer 包含相同類型的元素。
  • someContainer 中的元素可以通過(guò)不等于操作符(!=)來(lái)檢查它們是否彼此不同。

第三個(gè)和第四個(gè)要求結(jié)合起來(lái)意味著 anotherContainer 中的元素也可以通過(guò) != 操作符來(lái)比較,因?yàn)樗鼈兒?someContainer 中的元素類型相同。

這些要求讓 allItemsMatch(_:_:) 函數(shù)能夠比較兩個(gè)容器,即使它們的容器類型不同。

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ù)組是不同的類型,但它們都遵從 Container 協(xié)議,而且它們都包含相同類型的值。因此你可以用這兩個(gè)容器作為參數(shù)來(lái)調(diào)用 allItemsMatch(_:_:) 函數(shù)。在上面的例子中,allItemsMatch(_:_:) 函數(shù)正確地顯示了這兩個(gè)容器中的所有元素都是相互匹配的。

具有泛型 Where 子句的擴(kuò)展

你也可以使用泛型 where 子句作為擴(kuò)展的一部分。基于以前的例子,下面的示例擴(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é)議的類型都可以使用這個(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 為特定類型。例如:

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 類型為 Double 的容器中。此方法遍歷容器中的元素將其累加,并除以容器的數(shù)量計(jì)算平均值。它將數(shù)量從 Int 轉(zhuǎn)換為 Double 確保能夠進(jìn)行浮點(diǎn)除法。

就像可以在其他地方寫(xiě)泛型 where 子句一樣,你可以在一個(gè)泛型 where 子句中包含多個(gè)條件作為擴(kuò)展的一部分。用逗號(hào)分隔列表中的每個(gè)條件。

具有泛型 Where 子句的關(guān)聯(lián)類型

你可以在關(guān)聯(lián)類型后面加上具有泛型 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ú)論迭代器是什么類型,迭代器中的元素類型,必須和容器項(xiàng)目的類型保持一致。makeIterator() 則提供了容器的迭代器的訪問(wèn)接口。

一個(gè)協(xié)議繼承了另一個(gè)協(xié)議,你通過(guò)在協(xié)議聲明的時(shí)候,包含泛型 where 子句,來(lái)添加了一個(gè)約束到被繼承協(xié)議的關(guān)聯(lián)類型。例如,下面的代碼聲明了一個(gè) ComparableContainer 協(xié)議,它要求所有的 Item 必須是 Comparable 的。

protocol ComparableContainer: Container where Item: Comparable { }

泛型下標(biāo)

下標(biāo)能夠是泛型的,他們能夠包含泛型 where 子句。你可以把占位符類型的名稱寫(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)的約束如下:

  • 在尖括號(hào)中的泛型參數(shù) Indices,必須是符合標(biāo)準(zhǔn)庫(kù)中的 Sequence 協(xié)議的類型。
  • 下標(biāo)使用的單一的參數(shù),indices,必須是 Indices 的實(shí)例。
  • 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 類型。這樣就能確保在序列(Sequence)中的索引和容器(Container)里面的索引類型是一致的。

綜合一下,這些約束意味著,傳入到 indices 下標(biāo),是一個(gè)整型的序列。