22、【Swift】泛型 - Generics

  • 場景:類型參數(shù)化,增強代碼的復用性
  • 很多 Swift 標準庫是基于泛型構建
    • Array 和Dictionary 類型都是泛型集合
      • 可以創(chuàng)建一個容納 Int 值的數(shù)組,或者容納String 值的數(shù)組
      • 以創(chuàng)建一個存儲任何指定類型值的字典,而且類型沒有限制

泛型解決的問題

  • 非泛型例子
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
  • 使用
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
  • 非泛型例子
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
}
  • 函數(shù)體是一樣的。唯一的區(qū)別是它們接收值類型不同( Int 、String 和 Double )

若 a 和 b 類型不同,Swift 是類型安全語言,會引發(fā)一個編譯錯誤。

泛型函數(shù)

  • 上面提到的 swapTwoInts(::) 函數(shù)的泛型版本,叫做 swapTwoValues(::) :
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
  • 占位符類型名( T )
    • 不是實際類型名(比如Int 、 String 或 Double ),Swift 不會查找尖括號的 T 類型
    • a 和 b 必須都是同一個類型 T
    • 調用函數(shù)時,會根據(jù)實際數(shù)據(jù)類型,替代 T
  • 兩個例子中, T 分別被推斷為 Int 和 String :
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"

swap 函數(shù)是 Swift 標準庫的一部分,可直接用 swap(::) 函數(shù),不需要自己實現(xiàn)

類型參數(shù)

  • 類型參數(shù)普通參數(shù)類似
    • 命名,寫在函數(shù)名后面的尖括號里
    • 用作參數(shù)類型、返回值類型
    • 多個類型參數(shù),用逗號隔開

命名類型參數(shù)

  • 類型參數(shù)命名規(guī)范:
    • 要有描述性時:
      • 如 Dictionary<Key, Value> 中的Key 和 Value
    • 沒有關系描述時:
      • 一般按單個字母命名,如 T 、 U 、 V ,如上面 swapTwoValues(::) 函數(shù)中的 T

類型參數(shù)永遠用大寫開頭的駝峰命名法(如 T 和 MyTypeParameter ),以指明是一個類型的占位符,不是一個值。

泛型類型

  • 場景:自定義一個帶泛型的類型(相對于帶泛型的函數(shù))

  • 展示如何寫出一個叫做 Stack 的泛型集合類型

    • 數(shù)組允許在其中任何位置插入和移除元素。
    • 棧的新元素只能添加到集合的末尾(壓棧)。
    • 棧只允許從集合的末尾移除元素(出棧)。
  • 非泛型版本的棧,是一個 Int 值的棧

swstruct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}
  • 泛型版本:
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
  • 給尖括號傳參,創(chuàng)建一個新的字符串棧,可以寫 Stack<String>() :
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

泛型擴展

場景:給泛型類型寫擴展

  • 不需寫出類型參數(shù)的列表

  • 原(始)類型的類型參數(shù)可用于拓展

  • 添加一個叫做 topItem 的只讀計算屬性,不需要移除

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}
  • 擴展沒有定義類型形式參數(shù)列表。相反,擴展中用 Stack 已有的類型形式參數(shù)名稱, Element ,來指明計算屬性 topItem 的可選項類型。

類型約束

場景:限定泛型,必須繼承特定的類 or 遵守特定協(xié)議

  • Dictionary 需要它的鍵是可哈希的,以便它可以檢查字典中是否包含一個特定鍵的值
    • 指明了鍵類型必須遵循 Swift 標準庫中定義的 Hashable 協(xié)議
  • 所有 Swift 基本類型(比如 String 、 Int 、Double 和 Bool )默認都是可哈希的

類型約束語法

語法:類型形式參數(shù),名稱后面放一個類或協(xié)議,作為形式參數(shù)列表的一部分

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}
  • T ,要求 T 是SomeClass 的子類。 U ,要求 U 遵循SomeProtocol 協(xié)議。

類型約束實踐

給定字符串,它會返回數(shù)組中第一個匹配的字符串的索引值,如果找不到給定字符串就返回 nil :

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 只能用于字符串

  • T 類型的值代替所有用到的字符串,可以用泛型函數(shù)寫一個相同的功能

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 報運行時錯誤 - 原因:
    • 不是每種都能用相等操作符( == )來比較的,如類、結構體,相等無法推斷
  • 解決:
    • 遵循 Equatable 的協(xié)議
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 is not in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

關聯(lián)類型(協(xié)議的泛型參數(shù))

場景:定義協(xié)議不能像定義函數(shù)那樣,使用泛型參數(shù),素養(yǎng)使用關聯(lián)類型

報錯如下:

Protocols do not allow generic parameters; use associated types instead
  • 采納協(xié)議時,才推斷出關聯(lián)類型的實際類型
  • 通過 associatedtype 關鍵字指定

關聯(lián)類型實踐

  • 聲明了一個叫做 ItemType 的關聯(lián)類型:
protocol Container {
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
  • 前面非泛型版本的 IntStack ,使其遵循 Container 協(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 ItemType = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
  • 指定 ItemType 類型是 Int 。如果刪除 typealias ItemType = Int ,一切正常運行,因為 ItemType 該用什么類型能被推斷出來

  • 遵循 Container 協(xié)議的泛型 Stack 類型

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]
    }
}
  • Element 用于 append(_:) 方法的 item 形式參數(shù)和下標的返回類型
  • Swift 可以推斷出 Element 是適用于 ItemType 的類型

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

  • 場景:給協(xié)議的關聯(lián)類型,添加協(xié)議遵守或類型繼承
  • 定義了一個版本的 Container ,它要求容器中的元素都是可判等的
protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
  • 遵循這個版本的 Container ,容器的 Item 必須遵循 Equatable 協(xié)議

在關聯(lián)類型約束里使用協(xié)議

  • 場景:限定協(xié)議的關聯(lián)類型
  • 返回容器中從后往前給定數(shù)量的元素
protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
  • 關聯(lián)類型 Suffix 擁有兩個約束

    • 必須遵循 SuffixableContainer 協(xié)議(就是當前定義的協(xié)議)
    • Item 類型必須是和容器里的 Item 類型相同
  • 擴展添加了對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<Int> 作為它的后綴類型
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>.
}

擴展現(xiàn)有類型來指定關聯(lián)類型

  • 場景:為系統(tǒng)類型擴展功能
  • 擴展 Array ,使其遵循 Container 協(xié)議。通過一個空的擴展實現(xiàn)
extension Array: Container {}
  • 數(shù)組已有的 append(_:) 方法和下標使得Swift能為 ItemType 推斷出合適的類型
  • 可以把任何 Array 當做一個Container 使用

泛型 Where 語句

  • 場景:在泛型函數(shù)或泛型類型,約束泛型

  • Where分句-語法:

    • 后接關聯(lián)類型的約束或類型和關聯(lián)類型一致的關系
      • 遵循指定的協(xié)議
      • 指定的類型參數(shù)和關聯(lián)類型必須相同
    • 寫在一個類型或函數(shù)體的左半個大括號前面
  • 查兩個 Container 實例是否包含相同順序的相同元素

  • 兩個容器不一定是相同類型(盡管它們可以是),元素類型必須相同

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: 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 are equivalent.
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // All items match, so return true.
        return true
}
  • C1 的 ItemType 必須和 C2 的 ItemType 相同(寫作 C1.ItemType ==C2.ItemType );

  • C1 的 ItemType 必須遵循 Equatable 協(xié)議(寫作 C1.ItemType: Equatable )。

  • 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.")
}
// Prints "All items match."

具有泛型 Where 子句的擴展

  • 場景:擴展新功能時,給泛型添加約束條件

  • 添加了一個 isTop(_:) 方法

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {// 驗棧不為空
            return false
        }
        return topItem == item// 對比給定的元素與棧頂元素
    }
}
  • 使用
if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."
  • 在元素不能判等的棧調用 isTop(_:) 方法,運行時錯誤
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
  • 用泛型 where 分句來擴展到一個協(xié)議
extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}
  • 方法可以應用到任何遵循 Container 協(xié)議的類型上
if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// Prints "Starts with something else."
  • 除了要求 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"
  • 遍歷容器中的元素來把它們相加,然后除以容器的總數(shù)來計算平均值

  • 顯式地把總數(shù)從 Int 轉為Double 來允許浮點除法

  • 泛型 where 分句中包含多個要求來作為擴展的一部分,每一個需求用逗號分隔

包含上下文關系的 where 分句

  • 場景:簡化代碼,多個泛型類型的 extension 寫成一個extension
extension Container {
    func average() -> Double where Item == Int {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
    func endsWith(_ item: Item) -> Bool where Item: Equatable {
        return count >= 1 && self[count-1] == item
    }
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// Prints "648.75"
print(numbers.endsWith(37))
// Prints "true"
  • 在元素是整數(shù)時,給 Container 添加了一個 average() 方法
  • 在元素是可判等的情況下,添加了 endsWith(_:) 方法
  • 不使用上下文 where 分句,就需要寫兩個擴展,每一個都要用范型 where 分句
extension Container where Item == Int {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
}
extension Container where Item: Equatable {
    func endsWith(_ item: Item) -> Bool {
        return count >= 1 && self[count-1] == item
    }
}
  • 用了上下文 where 分句, average() 和 endsWith(_:) 都寫在了同一個擴展當中

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

  • 場景:給關聯(lián)類型添加約束

  • 要做一個包含遍歷器的Container,要求遍歷器 元素類型 = 容器元素類型

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
}
  • 給繼承的協(xié)議中關聯(lián)類型添加限定,要求 Item 遵循 Comparable :
protocol ComparableContainer: Container where Item: Comparable { }

泛型下標

語法:

  • 在 subscript 后用尖括號來寫類型占位符
  • 還可在花括號前寫泛型 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
    }
}
  • Container 協(xié)議的擴展添加了一個接收一系列索引并返回包含給定索引元素的數(shù)組
  • 泛型下班有如下限定
    • 泛型形式參數(shù) Indices 必須是遵循標準庫中 Sequence 協(xié)議的某類型
    • 泛型 where 分句要求序列的遍歷器元素,必須為 Int 類型的
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 泛型歷史和概述 泛型發(fā)展 泛型程序最早出現(xiàn)1970年代的CLU和Ada語言中,后來被許多基于對象和面向對象的語言所...
    迷心迷閱讀 671評論 0 0
  • 本章將會介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴展一個泛型類型類型約束關聯(lián)類型泛型 Where...
    寒橋閱讀 713評論 0 2
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 758評論 0 0
  • 一、泛型解決的問題 首先來看一個實際開發(fā)中經常遇到的簡單問題,這是一個標準的非泛型函數(shù)swapTwoInts(::...
    WSJay閱讀 2,149評論 0 3
  • 泛型代碼可以確保你寫出靈活的,可重用的函數(shù)和定義出任何你所確定好的需求的類型。你的可以寫出避免重復的代碼,并且用一...
    iOS_Developer閱讀 880評論 0 0

友情鏈接更多精彩內容