Swift 泛型

Swift 提供了泛型讓你寫出靈活且可重用的函式和型別。

Swift 標準庫是通過泛型程式碼構建出來的。

Swift 的陣列和字典型別都是泛型集。

你可以建立一個Int陣列,也可建立一個String陣列,或者甚至於可以是任何其他 Swift 的型別資料陣列。

以下例項是一個非泛型函式 exchange 用來交換兩個 Int 值:

例項

// 定義一個交換兩個變數的函式 func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("交換前資料: \(numb1) 和 \(numb2)") swapTwoInts(&numb1, &numb2) print("交換後資料: \(numb1) 和 \(numb2)")

以上程式執行輸出結果為:

交換前資料: 100 和 200
交換後資料: 200 和 100

以上例項只試用與交換整數 Int 型別的變數。如果你想要交換兩個 String 值或者 Double 值,就得重新寫個對應的函式,例如 swapTwoStrings(_:_:) 和 swapTwoDoubles(_:_:),如下所示:

String 和 Double 值交換函式

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 }

從以上程式碼來看,它們功能程式碼是相同的,只是型別上不一樣,這時我們可以使用泛型,從而避免重複編寫程式碼。

泛型使用了佔位型別名(在這裡用字母 T 來表示)來代替實際型別名(例如 Int、String 或 Double)。

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues 後面跟著佔位型別名(T),並用尖括號括起來(<T>)。這個尖括號告訴 Swift 那個 T 是 swapTwoValues(_:_:) 函式定義內的一個佔位型別名,因此 Swift 不會去查詢名為 T 的實際型別。

以下例項是一個泛型函式 exchange 用來交換兩個 Int 和 String 值:

例項

// 定義一個交換兩個變數的函式 func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("交換前資料: \(numb1) 和 \(numb2)") swapTwoValues(&numb1, &numb2) print("交換後資料: \(numb1) 和 \(numb2)") var str1 = "A" var str2 = "B" print("交換前資料: \(str1) 和 \(str2)") swapTwoValues(&str1, &str2) print("交換後資料: \(str1) 和 \(str2)")

以上程式執行輸出結果為:

交換前資料:  100 和 200
交換後資料: 200 和 100
交換前資料:  A 和 B
交換後資料: B 和 A

泛型型別

Swift 允許你定義你自己的泛型型別。

自定義類、結構體和列舉作用於任何型別,如同 Array 和 Dictionary 的用法。

接下來我們來編寫一個名為 Stack (棧)的泛型集合型別,棧只允許在集合的末端新增新的元素(稱之為入棧),且也只能從末端移除元素(稱之為出棧)。

圖片中從左到右解析如下:

  • 三個值在棧中。
  • 第四個值被壓入到棧的頂部。
  • 現在有四個值在棧中,最近入棧的那個值在頂部。
  • 棧中最頂部的那個值被移除,或稱之為出棧。
  • 移除掉一個值後,現在棧又只有三個值了。

以下例項是一個非泛型版本的棧,以 Int 型的棧為例:

Int 型的棧

struct IntStack { var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }

這個結構體在棧中使用一個名為 items 的 Array 屬性來儲存值。Stack 提供了兩個方法:push(_:) 和 pop(),用來向棧中壓入值以及從棧中移除值。這些方法被標記為 mutating,因為它們需要修改結構體的 items 陣列。

上面的 IntStack 結構體只能用於 Int 型別。不過,可以定義一個泛型 Stack 結構體,從而能夠處理任意型別的值。

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

泛型的棧

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() print("字串元素入棧: ") stackOfStrings.push("google") stackOfStrings.push("itread01") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("出棧元素: " + deletetos) var stackOfInts = Stack<Int>() print("整數元素入棧: ") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);

例項執行結果為:

字串元素入棧: 
["google", "itread01"]
出棧元素: itread01
整數元素入棧: 
[1, 2]

Stack 基本上和 IntStack 相同,佔位型別引數 Element 代替了實際的 Int 型別。

以上例項中 Element 在如下三個地方被用作佔位符:

  • 建立 items 屬性,使用 Element 型別的空陣列對其進行初始化。
  • 指定 push(_:) 方法的唯一引數 item 的型別必須是 Element 型別。
  • 指定 pop() 方法的返回值型別必須是 Element 型別。

擴充套件泛型型別

當你擴充套件一個泛型型別的時候(使用 extension 關鍵字),你並不需要在擴充套件的定義中提供型別引數列表。更加方便的是,原始型別定義中宣告的型別引數列表在擴充套件裡是可以使用的,並且這些來自原始型別中的引數名稱會被用作原始定義中型別引數的引用。

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

泛型

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings = Stack<String>() print("字串元素入棧: ") stackOfStrings.push("google") stackOfStrings.push("itread01") if let topItem = stackOfStrings.topItem { print("棧中的頂部元素是:\(topItem).") } print(stackOfStrings.items)

例項中 topItem 屬性會返回一個 Element 型別的可選值。當棧為空的時候,topItem 會返回 nil;當棧不為空的時候,topItem 會返回 items 陣列中的最後一個元素。

以上程式執行輸出結果為:

字串元素入棧: 
棧中的頂部元素是:itread01.
["google", "itread01"]

我們也可以通過擴充套件一個存在的型別來指定關聯型別。

例如 Swift 的 Array 型別已經提供 append(_:) 方法,一個 count 屬性,以及一個接受 Int 型別索引值的下標用以檢索其元素。這三個功能都符合 Container 協議的要求,所以你只需簡單地宣告 Array 採納該協議就可以擴充套件 Array。

以下例項建立一個空擴充套件即可:

extension Array: Container {}

型別約束

型別約束指定了一個必須繼承自指定類的型別引數,或者遵循一個特定的協議或協議構成。

型別約束語法

你可以寫一個在一個型別引數名後面的型別約束,通過冒號分割,來作為型別引數鏈的一部分。這種作用於泛型函式的型別約束的基礎語法如下所示(和泛型型別的語法相同):

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

上面這個函式有兩個型別引數。第一個型別引數 T,有一個要求 T 必須是 SomeClass 子類的型別約束;第二個型別引數 U,有一個要求 U 必須符合 SomeProtocol 協議的型別約束。

例項

泛型

// 非泛型函式,查詢指定字串在陣列中的索引 func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // 找到返回索引值 return index } } return nil } let strings = ["google", "weibo", "taobao", "itread01", "facebook"] if let foundIndex = findIndex(ofString: "itread01", in: strings) { print("itread01 的索引為 \(foundIndex)") }

索引下標從 0 開始。

以上程式執行輸出結果為:

itread01 的索引為 3

關聯類

Swift 中使用 associatedtype 關鍵字來設定關聯型別例項。

下面例子定義了一個 Container 協議,該協議定義了一個關聯型別 ItemType。

Container 協議只指定了三個任何遵從 Container 協議的型別必須提供的功能。遵從協議的型別在滿足這三個條件的情況下也可以提供其他額外的功能。

// Container 協議
protocol Container {
    associatedtype ItemType
    // 新增一個新元素到容器裡
    mutating func append(_ item: ItemType)
    // 獲取容器中元素的數
    var count: Int { get }
    // 通過索引值型別為 Int 的下標檢索到容器中的每一個元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 結構體遵從 Container 協議
struct Stack<Element>: Container {
    // Stack<Element> 的原始實現部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 協議的實現部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

var tos = Stack<String>()
tos.push("google")
tos.push("itread01")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素個數
print( tos.count)

以上程式執行輸出結果為:

["google", "itread01", "taobao"]
3

Where 語句

型別約束能夠確保型別符合泛型函式或類的定義約束。

你可以在引數列表中通過where語句定義引數的約束。

你可以寫一個where語句,緊跟在在型別引數列表後面,where語句後跟一個或者多個針對關聯型別的約束,以及(或)一個或多個型別和關聯型別間的等價(equality)關係。

例項

下面的例子定義了一個名為allItemsMatch的泛型函式,用來檢查兩個Container例項是否包含相同順序的相同元素。

如果所有的元素能夠匹配,那麼返回 true,反之則返回 false。

泛型

// Container 協議 protocol Container { associatedtype ItemType // 新增一個新元素到容器裡 mutating func append(_ item: ItemType) // 獲取容器中元素的數 var count: Int { get } // 通過索引值型別為 Int 的下標檢索到容器中的每一個元素 subscript(i: Int) -> ItemType { get } } // // 遵循Container協議的泛型TOS型別 struct Stack<Element>: Container { // Stack<Element> 的原始實現部分 var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Container 協議的實現部分 mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } // 擴充套件,將 Array 當作 Container 來使用 extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // 檢查兩個容器含有相同數量的元素 if someContainer.count != anotherContainer.count { return false } // 檢查每一對元素是否相等 for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // 所有元素都匹配,返回 true return true } var tos = Stack<String>() tos.push("google") tos.push("itread01") tos.push("taobao") var aos = ["google", "itread01", "taobao"] if allItemsMatch(tos, aos) { print("匹配所有元素") } else { print("元素不匹配") }

以上程式執行輸出結果為:

匹配所有元素