Swift 中的泛型


https://www.jianshu.com/p/a907f0c09a60

 

Swift泛型介紹

泛型是為Swift編程靈活性的一種語法,在函數、枚舉、結構體、類中都得到充分的應用,它的引入可以起到占位符的作用,當類型暫時不確定的,只有等到調用函數時才能確定具體類型的時候可以引入泛型。
我們之前實際上已經使用過泛型,例如:Swift的Array和Dictionary類型都是泛型集。

你可以創建一個Int數組,也可創建一個String數組,或者甚至於可以是任何其他Swift的類型數據數組。同樣的,你也可以創建存儲任何指定類型的字典(Dictionary),而且這些類型可以是沒有限制的。

我們為什么要使用泛型呢?下面有個例子可以簡單說明使用泛型的好處

// 定義一個函數,要求追加數組數據到指定一個數組中 func appendIntToArray(src:[Int],inout dest:[Int]) { // 遍歷並加到數組后邊 for element in src { dest.append(element) } } // 使用appendIntToArray添加整形數組數據 var arr = [2,5] appendIntToArray([12,9], dest: &arr) print(arr) // [2,5,12,9] 
// 那么再要求讓你實現添加字符串呢,好吧重寫一個 func appendStringToArray(src:[String],inout dest:[String]) { for element in src { dest.append(element) } } var strArr = ["OC","Swift"] appendStringToArray(["PHP", "C#"], dest: &strArr) print(strArr) 
// 如果有需要你實現添加其他類型呢? // 是不是每個類型都需要寫一個對應的函數去實現,那這樣就太復雜了!這時候我們就需要使用泛型 // 定義泛型函數,在普通函數名后面加上<T>,T是個類型占用符,可以表示任何類型 func appendArray<T>(src:[T],inout dest:[T]) { for element in src { dest.append(element) } } // 看到如此強大了吧?然后隨意使用 var arr2 = [5,8] appendArray([9,58], dest: &arr2) // appendArray自動識別要添加的數組數據類型 print(arr2) // [5, 8, 9, 58] var strArr2 = ["renhairui","hello"] appendArray(["nihao", "helloworld"], dest: &strArr2) print(strArr2) // ["renhairui", "hello", "nihao", "helloworld"] var doubleArr = [1.2,3.4] appendArray([6.5,1.0], dest: &doubleArr) print(doubleArr) // [1.2, 3.4, 6.5, 1.0] 

我的理解:泛型就是先占坑,具體占坑做什么,隨你

Swift泛型使用

Swift泛型相關使用可分為以下幾點:
泛型函數
泛型類型
泛型約束
泛型協議

泛型函數,函數參數或返回值類型用泛型表示

// 泛型函數定義式
func 函數名<泛型1,泛型2,…>(形參列表)->返回值類型
{
// 函數體...
}

泛型函數使用實例

// 定義一個泛型函數,把2個參數的值進行交換 func swapTwoValues<T>(inout valueOne:T ,inout valueTwo:T) { let temporaryA = valueOne valueOne = valueTwo valueTwo = temporaryA } var oneInt = 3 var twoInt = 107 swapTwoValues(&oneInt, valueTwo: &twoInt) print("oneInt = \(oneInt), twoInt = \(twoInt)") // oneInt = 107, twoInt = 3 var oneStr = "Hello" var twoStr = "World" swapTwoValues(&oneStr, valueTwo: &twoStr) print("oneStr = \(oneStr), twoStr = \(twoStr)") // oneStr = world, twoStr = hello 

泛型類型,在定義類型時使用泛型

使用也和泛型函數差不多,就是在類型名后面加上<泛型1,泛型2,…>,然后在類型里面直接使用泛型即可

通常在泛型函數中,Swift 允許你定義你自己的泛型類型。這些自定義類、結構體和枚舉作用於任何類型,如同Array和Dictionary的用法。
這部分向你展示如何寫一個泛型集類型--Stack(棧)。一個棧是一系列值域的集合,和Array(數組)類似,但其是一個比 Swift 的Array類型更多限制的集合。一個數組可以允許其里面任何位置的插入/刪除操作,而棧,只允許在集合的末端添加新的項(如同push一個新值進棧)。同樣的一個棧也只能從末端移除項(如同pop一個值出棧)。

注意 棧的概念已被UINavigationController類使用來模擬視圖控制器的導航結構。
你通過調用UINavigationController的pushViewController(:animated:)方法來為導航棧添加(add)新的視圖控制器;
而通過popViewControllerAnimated(
:)的方法來從導航棧中移除(pop)某個視圖控制器。
每當你需要一個嚴格的后進先出方式來管理集合,堆棧都是最實用的模型。

下圖展示了一個棧的壓棧(push)/出棧(pop)的行為:

 

現在有三個值在棧中;
第四個值“pushed”到棧的頂部;
現在有四個值在棧中,最近的那個在頂部;
棧中最頂部的那個項被移除,或稱之為“popped”;
移除掉一個值后,現在棧又重新只有三個值。

這里展示了如何寫一個非泛型版本的棧,Int值型的棧:

// 這里展示了如何寫一個非泛型版本的棧,Int值型的棧: struct IntStack { var items = [Int]() mutating func push(item:Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } } 

這個結構體在棧中使用一個Array性質的items存儲值。Stack提供兩個方法:push和pop,從棧中壓進一個值和移除一個值。這些方法標記為可變的,因為它們需要修改(或轉換)結構體的items數組。
上面所展現的IntStack類型只能用於Int值,不過,其對於定義一個泛型Stack類(可以處理任何類型值的棧)是非常有用的。

這里是一個相同代碼的泛型版本

// 這里是一個相同代碼的泛型版本: struct Stack<T> { var items = [T]() mutating func push(item:T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } } 

注意到Stack的泛型版本基本上和非泛型版本相同,但是泛型版本的占位類型參數為T代替了實際Int類型。這種類型參數包含在一對尖括號里(<T>),緊隨在結構體名字后面。
T定義了一個名為“某種類型T”的節點提供給后來用。這種將來類型可以在結構體的定義里任何地方表示為“T”。在這種情況下,T在如下三個地方被用作節點:

  • 創建一個名為items的屬性,使用空的T類型值數組對其進行初始化;
  • 指定一個包含一個參數名為item的push(_:)方法,該參數必須是T類型;
  • 指定一個pop方法的返回值,該返回值將是一個T類型值。

由於Stack是泛型類型,所以在 Swift 中其可以用來創建任何有效類型的棧,這種方式如同Array和Dictionary。
你可以通過在尖括號里寫出棧中需要存儲的數據類型來創建並初始化一個Stack實例。比如,要創建一個strings的棧,你可以寫成Stack<String>():

var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") // 現在棧已經有4個string了 

下圖將展示stackOfStrings如何push這四個值進棧的過程:

 

從棧中pop並移除值"cuatro"

let fromTop = stackOfStrings.pop()
// fromTheTop 等於 "cuatro", 現在棧中還有3個string

下圖展示了如何從棧中pop一個值的過程:

 

擴展一個泛型類型

當你擴展一個泛型類型的時候,你並不需要在擴展的定義中提供類型參數列表。更加方便的是,原始類型定義中聲明的類型參數列表在擴展里是可以使用的,並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用。

下面的例子擴展了泛型Stack類型,為其添加了一個名為topItem的只讀計算屬性,它將會返回當前棧頂端的元素而不會將其從棧中移除。

extension Stack { var topItem:T? { return items.isEmpty ? nil :items[items.count - 1] } } if let topItem = stackOfStrings.topItem { print("The top item on the stack is \(topItem).") } // 輸出 "The top item on the stack is tres." 

泛型約束,為泛型類型添加約束

泛型約束大致分為以下幾種:
繼承約束,泛型類型必須是某個類的子類類型
協議約束,泛型類型必須遵循某些協議
條件約束,泛型類型必須滿足某種條件
約束的大概使用格式

// 繼承約束使用格式
func 函數名<泛型: 繼承父類>(參數列表) -> 返回值 {
// 函數體,泛型類型是某個類的子類類型
}
// 協議約束使用格式
func 函數名<泛型: 協議>(參數列表) -> 返回值 {
// 函數體,泛型類型遵循某些協議
}
// 條件約束使用格式
func 函數名<泛型1, 泛型2 where 條件>(參數列表) -> 返回值 {
// 函數體,泛型類型滿足某些條件

}

繼承約束使用范例

// 繼承約束使用范例 // 定義一個父類,動物類 class Animal { // 動物都會跑 func run() { print("Animal run") } } class Dog :Animal { override func run() { // 重寫父類方法 print("Dog run") } } class Cat :Animal { override func run() { print("Cat run") } } // 定義泛型函數,接受一個泛型參數,要求該泛型類型必須繼承Animal func AnimalRunPint<T:Animal>(animal:T) { animal.run() // 繼承了Animal類的子類都有run方法可以調用 } AnimalRunPint(Dog()) // Dog run AnimalRunPint(Cat()) // Cat run 

協議約束使用范例
Swift標准庫中定義了一個Equatable協議,該協議要求任何遵循的類型實現等式符(==)和不等符(!=)對任何兩個該類型進行比較。所有的Swift標准類型自動支持Equatable協議。

// 協議約束使用范例 // 定義泛型函數,為泛型添加協議約束,泛型類型必須遵循Equatable協議 func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? { var index = 0 for value in array { if value == valueToFind { // 因為遵循了Equatable協議,所以可以進行相等比較 return index } else { index++ } } return nil } // 在浮點型數組中進行查找,Double默認遵循了Equatable協議 let doubleIndex = findIndex([3.14159, 0.1, 0.25], valueToFind: 9.3) if let index = doubleIndex { print("在浮點型數組中尋找到9.3,尋找索引為\(index)") } else { print("在浮點型數組中尋找不到9.3") } // 在字符串數組中進行查找,String默認遵循了Equatable協議 let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], valueToFind: "Andrea") if let index = stringIndex { print("在字符串數組中尋找到Andrea,尋找索引為\(index)") } else { print("在字符串數組中尋找不到Andrea") } /* 打印: 在浮點型數組中尋找不到9.3 在字符串數組中尋找到Andrea,尋找索引為2 */ 

泛型協議和條件約束

上面的Equatable協議實際上不是普通的協議,而是泛型協議,假設泛型類型必須遵循一個協議,此時就必須在協議中引入一個關聯類型來解決。

// 定義一個泛型協議,和其他泛型使用方式不同,這里泛型是以關聯類型形式使用的 protocol Stackable{ // 聲明一個關聯類型,使用typealias關鍵字 typealias ItemType mutating func push(item:ItemType) mutating func pop() -> ItemType } struct Stack<T>:Stackable{ var store = [T]() mutating func push(item:T){ // 實現協議的push方法要求 store.append(item) } mutating func pop() -> T { // 實現協議的pop方法要求 return store.removeLast() } } // 創建Stack結構體,泛型類型為String var stackOne = Stack<String>() stackOne.push("hello") stackOne.push("swift") stackOne.push("world") let t = stackOne.pop() print("t = \(t)") //結果:t = world // 添加泛型條件約束,C1和C2必須遵循Stackable協議,而且C1和C2包含的泛型類型要一致 func pushItemOneToTwo<C1: Stackable, C2: Stackable where C1.ItemType == C2.ItemType>(inout stackOne: C1, inout stackTwo: C2) { // 因為C1和C2都遵循了Stackable協議,才有ItemType屬性可以調用 let item = stackOne.pop() stackTwo.push(item) } // 定義另外一個結構體類型,同樣實現Stackable協議,實際上里面的實現和Stack一樣 struct StackOther<T>: Stackable{ var store = [T]() mutating func push(item:T){ // 實現協議的push方法要求 store.append(item) } mutating func pop() -> T { // 實現協議的pop方法要求 return store.removeLast() } } // 創建StackOther結構體,泛型類型為String var stackTwo = StackOther<String>() stackTwo.push("where") // 雖然stackOne和stackTwo類型不一樣,但泛型類型一樣,也同樣遵循了Stackable協議 pushItemOneToTwo(&stackOne, stackTwo: &stackTwo ) print("stackOne = \(stackOne.store), stackTwo = \(stackTwo.store)") // 打印:stackOne = ["hello"], stackTwo = ["where", "swift"]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM