//泛型 Generics
// “泛型代碼讓你能夠根據(jù)自定義的需求耀怜,編寫出適用于任意類型从绘、靈活可重用的函數(shù)及類型。它能讓你避免代碼的重復(fù)涣觉,用一種清晰和抽象的方式來表達(dá)代碼的意圖贵涵×兄”
//“泛型是 Swift 最強大的特性之一恰画,許多 Swift 標(biāo)準(zhǔn)庫是通過泛型代碼構(gòu)建的”
//“泛型的使用貫穿了整本語言手冊.例如,Swift 的 Array 和 Dictionary 都是泛型集合瓷马。你可以創(chuàng)建一個 Int 數(shù)組拴还,也可創(chuàng)建一個 String 數(shù)組,甚至可以是任意其他 Swift 類型的數(shù)組欧聘。同樣的自沧,你也可以創(chuàng)建存儲任意指定類型的字典
//1.泛型解決的問題
func swapTwoInts(_ a:inout Int, _ b: inout Int){
let temperatyA = a
a = b
b = temperatyA
}
//“這個函數(shù)使用輸入輸出參數(shù)(inout)來交換 a 和 b 的值
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt),anotherInt is now \(anotherInt)")
//“誠然,swapTwoInts(::) 函數(shù)挺有用树瞭,但是它只能交換 Int 值拇厢,如果你想要交換兩個 String 值或者 Double值,就不得不寫更多的函數(shù)”
//“在實際應(yīng)用中晒喷,通常需要一個更實用更靈活的函數(shù)來交換兩個任意類型的值孝偎,幸運的是,泛型代碼幫你解決了這種問題”
//2. 泛型函數(shù)
//“泛型函數(shù)可以適用于任何類型
func swapTwoValues<T>(_ a:inout T,_ b:inout T){
let temporaryA = a
a = b
b = temporaryA
}
//“swapTwoValues(::) 的函數(shù)主體和 swapTwoInts(::) 函數(shù)是一樣的凉敲,它們只在第一行有點不同衣盾,如下所示:
//func swapTwoInts(_ a: inout Int, _ b: inout Int)
//func swapTwoValues<T>(_ a: inout T, _ b: inout T)
//“這個函數(shù)的泛型版本使用了占位類型名(在這里用字母 T 來表示)來代替實際類型名(例如 Int、String 或 Double)爷抓。占位類型名沒有指明 T 必須是什么類型势决,但是它指明了 a 和 b 必須是同一類型 T,無論 T 代表什么類型蓝撇。只有 swapTwoValues(::) 函數(shù)在調(diào)用時果复,才能根據(jù)所傳入的實際類型決定 T 所代表的類型〔巢”
//“另外一個不同之處在于這個泛型函數(shù)名(swapTwoValues(::))后面跟著占位類型名(T)虽抄,并用尖括號括起來(<T>)。這個尖括號告訴 Swift 那個 T 是 swapTwoValues(::) 函數(shù)定義內(nèi)的一個占位類型名独柑,因此 Swift 不會去查找名為 T 的實際類型迈窟。”
var someString = "jack"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
//“注意 上面定義的 swapTwoValues(::) 函數(shù)是受 swap(::) 函數(shù)啟發(fā)而實現(xiàn)的忌栅。后者存在于 Swift 標(biāo)準(zhǔn)庫车酣,你可以在你的應(yīng)用程序中使用它∷餍鳎”
//3. 類型參數(shù)
//“在上面的 swapTwoValues(::) 例子中湖员,占位類型 T 是類型參數(shù)的一個例子。類型參數(shù)指定并命名一個占位類型者春,并且緊隨在函數(shù)名后面破衔,使用一對尖括號括起來(例如 <T>)∏蹋”
//“一旦一個類型參數(shù)被指定,你可以用它來定義一個函數(shù)的參數(shù)類型(例如 swapTwoValues(::) 函數(shù)中的參數(shù) a 和 b),或者作為函數(shù)的返回類型拴袭,還可以用作函數(shù)主體中的注釋類型读第。在這些情況下,類型參數(shù)會在函數(shù)調(diào)用時被實際類型所替換拥刻×鳎”
//“你可提供多個類型參數(shù),將它們都寫在尖括號中般哼,用逗號分開吴汪。”
//4.命名類型參數(shù)
//“在大多數(shù)情況下蒸眠,類型參數(shù)具有一個描述性名字漾橙,例如 Dictionary<Key, Value> 中的 Key 和 Value,以及 Array<Element> 中的 Element楞卡,這可以告訴閱讀代碼的人這些類型參數(shù)和泛型函數(shù)之間的關(guān)系霜运。然而,當(dāng)它們之間沒有有意義的關(guān)系時蒋腮,通常使用單個字母來命名淘捡,例如 T、U池摧、V焦除,正如上面演示的 swapTwoValues(::) 函數(shù)中的 T 一樣∽魍”
//5. 泛型類型
//“除了泛型函數(shù)踢京,Swift 還允許你定義泛型類型。這些自定義類宦棺、結(jié)構(gòu)體和枚舉可以適用于任何類型瓣距,類似于 Array 和 Dictionary”
//“這部分內(nèi)容將向你展示如何編寫一個名為 Stack (棧)的泛型集合類型”
//“棧是一系列值的有序集合,和 Array 類似代咸,但它相比 Swift 的 Array 類型有更多的操作限制蹈丸。數(shù)組允許在數(shù)組的任意位置插入新元素或是刪除其中任意位置的元素。而棧只允許在集合的末端添加新的元素(稱之為入棧)呐芥。類似的逻杖,棧也只能從末端移除元素(稱之為出棧)∷嘉粒”
//“注意 棧的概念已被 UINavigationController 類用來構(gòu)造視圖控制器的導(dǎo)航結(jié)構(gòu)荸百。你通過調(diào)用 UINavigationController 的 pushViewController(:animated:) 方法來添加新的視圖控制器到導(dǎo)航棧,通過 popViewControllerAnimated(:) 方法來從導(dǎo)航棧中移除視圖控制器滨攻。每當(dāng)你需要一個嚴(yán)格的“后進(jìn)先出”方式來管理集合够话,棧都是最實用的模型蓝翰。”
struct IntStack{
var items = [Int]()
mutating func push(_ item:Int){
items.append(item)
}
mutating func pop()->Int{
return items.removeLast()
}
}
//6.泛型版本
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 代替了實際的 Int 類型。這個類型參數(shù)包裹在緊隨結(jié)構(gòu)體名的一對尖括號里(<Element>)欣尼”ⅲ”
//“由于 Stack 是泛型類型,因此可以用來創(chuàng)建 Swift 中任意有效類型的棧愕鼓,就像 Array 和 Dictionary 那樣钙态。”
//“你可以通過在尖括號中寫出棧中需要存儲的數(shù)據(jù)類型來創(chuàng)建并初始化一個 Stack 實例”
var StackOfStings = Stack<String>()
StackOfStings.push(item: "aa")
StackOfStings.push(item: "bb")
StackOfStings.push(item: "cc")
StackOfStings.pop()
//7. 擴(kuò)展一個泛型類型
//“當(dāng)你擴(kuò)展一個泛型類型的時候菇晃,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表册倒。原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展中可以直接使用,并且這些來自原始類型中的參數(shù)名稱會被用作原始定義中類型參數(shù)的引用”
extension Stack {
var topItem : Element?{
return items.isEmpty ? nil : items[items.count - 1]
}
}
//“注意谋旦,這個擴(kuò)展并沒有定義一個類型參數(shù)列表剩失。相反的,Stack 類型已有的類型參數(shù)名稱 Element册着,被用在擴(kuò)展中來表示計算型屬性 topItem 的可選類型拴孤。”
if let topItem = StackOfStings.topItem {
print("the top item on stack is \(topItem)")
}
//打印 the top item on stack is bb
//8. 類型約束
//“swapTwoValues(::) 函數(shù)和 Stack 類型可以作用于任何類型甲捏。不過演熟,有的時候如果能將使用在泛型函數(shù)和泛型類型中的類型添加一個特定的類型約束,將會是非常有用的司顿。類型約束可以指定一個類型參數(shù)必須繼承自指定類芒粹,或者符合一個特定的協(xié)議或協(xié)議組合〈罅铮”
//“例如化漆,Swift 的 Dictionary 類型對字典的鍵的類型做了些限制。在字典的描述中钦奋,字典的鍵的類型必須是可哈希(hashable)的座云。也就是說,必須有一種方法能夠唯一地表示它付材。Dictionary 的鍵之所以要是可哈希的朦拖,是為了便于檢查字典是否已經(jīng)包含某個特定鍵的值。若沒有這個要求厌衔,Dictionary 將無法判斷是否可以插入或者替換某個指定鍵的值璧帝,也不能查找到已經(jīng)存儲在字典中的指定鍵的值「皇伲”
//“為了實現(xiàn)這個要求睬隶,一個類型約束被強制加到 Dictionary 的鍵類型上锣夹,要求其鍵類型必須符合 Hashable 協(xié)議,這是 Swift 標(biāo)準(zhǔn)庫中定義的一個特定協(xié)議理疙。所有的 Swift 基本類型(例如 String晕城、Int泞坦、Double 和 Bool)默認(rèn)都是可哈希的窖贤。”
//8.1 類型約束語法
//“你可以在一個類型參數(shù)名后面放置一個類名或者協(xié)議名贰锁,并用冒號進(jìn)行分隔赃梧,來定義類型約束,它們將成為類型參數(shù)列表的一部分”
//func someFunction<T:SomeClass,U:SomeProtocol>(someT:T,someU:U){
//這里是泛型的函數(shù)體
//}
//“第一個類型參數(shù) T豌熄,有一個要求 T 必須是 SomeClass 子類的類型約束授嘀;第二個類型參數(shù) U,有一個要求 U 必須符合 SomeProtocol 協(xié)議的類型約束锣险√阒澹”
//8.2 類型約束實踐
func findIndex(ofString valueToFind:String,in array:[String])->Int?{
for (index,value) in array.enumerated() {
if value == valueToFind{
return index
}
}
return nil
}
let strings = ["cat","dog","lima","jack"]
if let foundIndex = findIndex(ofString: "jack", in: strings) {
print("the index of jack is \(foundIndex)")
}
//泛型版本
//func findIndexGenerics<T>(of valueToFind:T,in array:[T])->Int?{
// for (index,value) in array.enumerated() {
// if value == valueToFind{
// return index
// }
// }
// return nil
//}
//“上面所寫的函數(shù)無法通過編譯。問題出在相等性檢查上芯肤,即 "if value == valueToFind"巷折。不是所有的 Swift 類型都可以用等式符(==)進(jìn)行比較,“比如說,如果你創(chuàng)建一個自定義的類或結(jié)構(gòu)體來表示一個復(fù)雜的數(shù)據(jù)模型崖咨,那么 Swift 無法猜到對于這個類或結(jié)構(gòu)體而言“相等”意味著什么锻拘。正因如此,這部分代碼無法保證適用于每個可能的類型 T击蹲,當(dāng)你試圖編譯這部分代碼時會出現(xiàn)相應(yīng)的錯誤署拟。”
//“不過歌豺,所有的這些并不會讓我們無從下手推穷。Swift 標(biāo)準(zhǔn)庫中定義了一個 Equatable 協(xié)議,該協(xié)議要求任何遵循該協(xié)議的類型必須實現(xiàn)等式符(==)及不等符(!=)类咧,從而能對該類型的任意兩個值進(jìn)行比較馒铃。所有的 Swift 標(biāo)準(zhǔn)類型 自動支持 Equatable 協(xié)議÷痔”
func findIndexGenerices<T:Equatable>(array:[T],_ valuetoFind:T)->Int?{
for (index,value) in array.enumerated() {
if value == valuetoFind {
return index
}
}
return nil
}
let doubleIndex = findIndexGenerices(array: [3,14159,0.1,0.25], 9.3)
//doubleIndex的值為nil
let stingIndex = findIndexGenerices(array: ["mike","jordan","rose"], "rose")
//stingIndex類型為int骗露? 值為2
//9. 關(guān)聯(lián)類型
//“定義一個協(xié)議時,有的時候聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分將會非常有用”“關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位名(或者說別名)血巍,其代表的實際類型在協(xié)議被采納時才會被指定萧锉。你可以通過 associatedtype 關(guān)鍵字來指定關(guān)聯(lián)類型∈龉眩”
protocol Container{
associatedtype itemType
mutating func append(item:itemType)
var count : Int{get}
subscript(i:Int)->itemType{get}
}
struct IntStackGenerics:Container {
var items = [Int]()
mutating func push(item:Int){
items.append(item)
}
mutating func pop()->Int{
return items.removeLast()
}
//Container協(xié)議實現(xiàn)部分
typealias itemType = Int
mutating func append(item: Int) {
self.push(item: 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 的要求時禀崖,指定 ItemType 為 Int 類型衩辟,即 typealias ItemType = Int,從而將 Container 協(xié)議中抽象的 ItemType 類型轉(zhuǎn)換為具體的 Int 類型波附∫涨纾”
//“由于 Swift 的類型推斷,你實際上不用在 IntStack 的定義中聲明 ItemType 為 Int掸屡。因為 IntStack 符合 Container 協(xié)議的所有要求封寞,Swift 只需通過 append(_:) 方法的 item 參數(shù)類型和下標(biāo)返回值的類型,就可以推斷出 ItemType 的具體類型仅财。事實上狈究,如果你在上面的代碼中刪除了 typealias ItemType = Int 這一行,一切仍舊可以正常工作盏求,因為 Swift 清楚地知道 ItemType 應(yīng)該是哪種類型抖锥。”
//也可以讓泛型Stack結(jié)構(gòu)體遵從Container協(xié)議
struct StackGenerics<Element>:Container{
//StackCenerics<Element>的原始部分
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: StackGenerics<Element>.itemType) {
self.push(item: item)
}
var count: Int{
return items.count
}
subscript(i:Int)->Element{
return items[i]
}
}
//“這一次碎罚,占位類型參數(shù) Element 被用作 append(_:) 方法的 item 參數(shù)和下標(biāo)的返回類型磅废。Swift 可以據(jù)此推斷出 Element 的類型即是 ItemType 的類型。
//9.2 “通過擴(kuò)展一個存在的類型來指定關(guān)聯(lián)類型”
//“通過擴(kuò)展添加協(xié)議一致性中描述了如何利用擴(kuò)展讓一個已存在的類型符合一個協(xié)議魂莫,這包括使用了關(guān)聯(lián)類型的協(xié)議还蹲。”
//“Swift 的 Array 類型已經(jīng)提供 append(:) 方法耙考,一個 count 屬性谜喊,以及一個接受 Int 類型索引值的下標(biāo)用以檢索其元素。這三個功能都符合 Container 協(xié)議的要求倦始,也就意味著你只需簡單地聲明 Array 采納該協(xié)議就可以擴(kuò)展 Array斗遏,使其遵從 Container 協(xié)議。你可以通過一個空擴(kuò)展來實現(xiàn)這點鞋邑,正如通過擴(kuò)展采納協(xié)議中的描述:”
extension Array:Container{}
//“如同上面的泛型 Stack 結(jié)構(gòu)體一樣诵次,Array 的 append(:) 方法和下標(biāo)確保了 Swift 可以推斷出 ItemType 的類型。定義了這個擴(kuò)展后枚碗,你可以將任意 Array 當(dāng)作 Container 來使用逾一。”
//10.泛型Where語句
//“類型約束讓你能夠為泛型函數(shù)或泛型類型的類型參數(shù)定義一些強制要求肮雨∽穸拢”
//“為關(guān)聯(lián)類型定義約束也是非常有用的。你可以在參數(shù)列表中通過 where 子句為關(guān)聯(lián)類型定義約束”
//“你能通過 where 子句要求一個關(guān)聯(lián)類型遵從某個特定的協(xié)議,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同”
//“你可以通過將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來定義 where 子句陌宿,where 子句后跟一個或者多個針對關(guān)聯(lián)類型的約束锡足,以及一個或多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系】瞧海”
//“你可以在函數(shù)體或者類型的大括號之前添加 where 子句舶得。”
func allItemsMatch<C1:Container,C2:Container>(_ someContainer:C1,_ anotherContainer:C2)->Bool where C1.itemType == C2.itemType,C1.itemType:Equatable{
//檢查兩個容器含有相同數(shù)量的元素
if someContainer.count != anotherContainer.count {
return false
}
//檢查每一對元素是否相等
for i in 0..<someContainer.count{
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
/*
“C1 必須符合 Container 協(xié)議(寫作 C1: Container)爽蝴。
C2 必須符合 Container 協(xié)議(寫作 C2: Container)沐批。
C1 的 ItemType 必須和 C2 的 ItemType類型相同(寫作 C1.ItemType == C2.ItemType)。
C1 的 ItemType 必須符合 Equatable 協(xié)議(寫作 C1.ItemType: Equatable)霜瘪。
*/
//“第三個和第四個要求被定義為一個 where 子句珠插,寫在關(guān)鍵字 where 后面惧磺,它們也是泛型函數(shù)類型參數(shù)列表的一部分”