【Swift 3.1】23 - 泛型 (Generics)

【Swift 3.1】23 - 泛型 (Generics)

自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來到了3.1版本。去年利用工作之余,共花了兩個(gè)多月的時(shí)間把官方的Swift編程指南看完?,F(xiàn)在整理一下筆記,回顧一下以前的知識(shí)。有需要的同學(xué)可以去看官方文檔>>。


泛型代碼能讓我們編寫靈活、可重復(fù)使用的方法和類型??梢员苊饩帉懼貜?fù)代碼,并以清晰和抽象的方法表達(dá)其目的。

泛型是Swift非常強(qiáng)大的一個(gè)特性,并且很多Swift標(biāo)準(zhǔn)庫都是用泛型編寫的。實(shí)際上我們已經(jīng)使用過泛型,例如Swift的ArrayDictionary都是泛型集合。

泛型能解決的問題 (The Problem That Generics Solve)

下面這個(gè)方法只能交換兩個(gè)Int值,使用in-out參數(shù)來交換ab

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

上面這個(gè)方法只能交換Int,如果我想交換兩個(gè)String類型的值或者兩個(gè)Double類型的值,我們又得寫兩個(gè)方法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
}

使用泛型代碼讓我們用一個(gè)方法就能解決上面的問題。

泛型方法 (Generic Functions)

上面的三個(gè)方法可以用下面的額泛型方法代替:

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

這個(gè)通用的方法使用T作為類型名稱,而不是IntString或者Double。T沒有說明必須是什么類型,但是ab的類型必須是一樣的。

方法名后面還有一個(gè)<T>,告訴Swift只是一個(gè)占位類型,不是實(shí)際類型。

swapTwoValues(_:_:)方法可以像之前的swapTwoInts一樣調(diào)用,只要兩個(gè)用于交換的值類型是一樣的。

用通用的方法交換兩個(gè)String類型的值或者兩個(gè)Double類型的值:

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"

注意:其實(shí)Swift的標(biāo)準(zhǔn)庫中已經(jīng)有一個(gè)功能與swapTwoValues(_:_:)一樣的方法swap。如果我們需要交換兩個(gè)值,直接使用swap即可,無需自己另外實(shí)現(xiàn)這個(gè)功能。

類型參數(shù) (Type Parameters)

上面的泛型方法swapTwoValues(_:_:),占位類型T其實(shí)是一個(gè)類型參數(shù)。類型參數(shù)指定并命名占位類型,緊跟在方法名后面,用<T>表示。

只要定義好了類型參數(shù),我們就可以用來定義方法的參數(shù)類型(例如swapTwoValues(_:_:)的參數(shù)ab的類型),或者作為方法的返回類型。

我們可以定義多個(gè)類型參數(shù),寫法是:<T1, T2, ...>。

類型參數(shù)命名 (Type Parameters)

在很多情況下,類型參數(shù)有描述性的名稱,例如Dictionary<Key, Value>中的KeyValue,Array<Element>中的Element,這些名字都能告訴讀者類型參數(shù)和泛型的關(guān)系。但是,在沒有任何意義的情況下,我們一般把類型參數(shù)名命名為TUV。

注意:需要使用駱駝命名法給類型參數(shù)命名,例如TMyTypeParameter,以表示他們是一個(gè)類型的占位。

泛型類型 (Generic Types)

除了泛型方法,Swift還可以定義泛型類型,例如ArrayDictionary。

這一部分將演示如何寫一個(gè)泛型集合Stack。一個(gè)棧是一個(gè)有序的值的集合,類似一個(gè)數(shù)組,但是比數(shù)組有更嚴(yán)格的運(yùn)算。 數(shù)組可以在特定的位置移除或插入元素。而棧只允許在集合的最后添加元素,也只允許從最后面移除元素。

注意:棧的概念就被用于UINavigationController管理控制器。使用pushViewController(_:animated:)來添加新的控制器到棧中,用popViewControllerAnimated(_:)移除控制器。棧非常適合用來管理“后進(jìn)先去”的集合。

下圖演示了棧的push/pop:

Stack
  1. 目前棧中有三個(gè)值
  2. 第四個(gè)值被push到棧頂
  3. 棧中有四個(gè)值
  4. 第四個(gè)值被移除(popped)
  5. 移除第四個(gè)值后,棧中剩下三個(gè)值

下面是一個(gè)不通用的IntStack:

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

下面是一個(gè)通用版本的Stack:

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

Element定義了一個(gè)占位名字,并且在三個(gè)地方被用到:

  • 創(chuàng)建一個(gè)空數(shù)組items,其元素類型為Element
  • 作為push(_:)方法的參數(shù)類型
  • 作為pop()方法的返回值

因?yàn)樗且粋€(gè)通用類型,所以Stack可以用來創(chuàng)建任意有效類型的棧:

var stackOfThings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings

下面是stackOfThings的push過程:

push

移除棧頂?shù)闹?code>"cuatro":

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

移除過程如下:

pop

擴(kuò)展泛型類型 (Extending a Generic Type)

在擴(kuò)展泛型類型是,我們不必提供類型參數(shù),可以直接使用已有泛型類型的類型參數(shù)。

下面是對(duì)Stack的擴(kuò)展:

extension Stack {
    va topItem: Element? {
        return items.isEnpty ? nil : items[items.count - 1]
    }
}

訪問topItem屬性:

if let topItem = stackOfStrings.topItem {
    print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."

泛型類型的擴(kuò)展也可以指定一些限定條件,滿足這些條件才可以訪問擴(kuò)展里面的成員,下面會(huì)講到。

類型約束 (Type Constraints)

swapTwoValues(_:_:)Stack可以用于任何類型。然而,在某些類型上添加一些類型約束是非常有用的。類型約束指定一個(gè)類型參數(shù)必須繼承于特定的類或者遵循特定的協(xié)議。

例如,Swift的Dictionary限定了鍵的類型。字典的鍵必須是hashable的,也就是說,必須提供一個(gè)方法保證它自己是唯一的,然后字典才能根據(jù)這個(gè)鍵去查找對(duì)應(yīng)的值。Swift的基本類型默認(rèn)都是hashable的,例如StringInt、DoubleBool。

類型約束語法 (Type Constraint Syntax)

語法如下:

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

上面的語法要求T必須是SomeClass的子類,U必須遵循SomeProtocol協(xié)議。

類型約束實(shí)踐 (Type Constraint in Action)

下面是一個(gè)不通用的findIndex(ofString:in:),用于查找指定字符串在字符串?dāng)?shù)組的索引:

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", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"

我們可以把上面的方法改為通用形式:

func findIndex<T>(of valueToFind: T in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return value
        }
    }
    return nil
}

其實(shí)這個(gè)方法不能編譯通過,因?yàn)?code>value == valueToFind。不是所有Swift類型的都可使用==進(jìn)行比較。

Swift定義了Equatable協(xié)議,要求所有遵循這個(gè)協(xié)議的類型必須實(shí)現(xiàn)==!=運(yùn)算符。Swift的標(biāo)準(zhǔn)類型都遵循了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
}

T: Equatable類型參數(shù)意味著任意類型T必須遵循Equatalbe協(xié)議。

例如:

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

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

當(dāng)定義協(xié)議時(shí),定義一個(gè)或多個(gè)關(guān)聯(lián)類型有時(shí)候是非常有用的。關(guān)聯(lián)類型提供了一個(gè)類型名字,然后這個(gè)類型可以在協(xié)議中使用。使用associatedtype關(guān)鍵字來定義關(guān)聯(lián)類型。

關(guān)聯(lián)類型實(shí)踐 (Associated Types in Action)

下面是一個(gè)例子:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

這個(gè)容器協(xié)議中沒有具體說明能存儲(chǔ)那個(gè)類型的值。遵循這個(gè)協(xié)議的類型必須寫清楚能存儲(chǔ)的值類型。

之前那個(gè)IntStack類型遵循Container協(xié)議:

struct Instack: Container {
    // original Instack 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 Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStack指定了Item是一個(gè)Int類型。typealias Item = Int把抽象的Item轉(zhuǎn)變?yōu)榫唧w的Int。

因?yàn)镾wift有類型推斷功能,所以我們可以不用寫typealias Item = Int,因?yàn)?code>IntStack實(shí)現(xiàn)了Container協(xié)議的所有要求,Swift能推斷出需要用的Item的具體類型。

之前那個(gè)Stack類型遵循Container協(xié)議:

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]
    }
}
通過擴(kuò)展類型來指定關(guān)聯(lián)類型 (Extending an Existing Type to Specifying an Associated Type)

我們可以使用擴(kuò)展來遵循某一個(gè)協(xié)議,當(dāng)然這個(gè)協(xié)議也可以是由關(guān)聯(lián)類型的協(xié)議。

Swift的Array已經(jīng)提供了append(_:)方法、count屬性和下標(biāo),那么就滿足了Container協(xié)議的要求。所以我們可以是下面這種形式來聲明Array遵循Container協(xié)議:

extension Array: Container {}

我們就可以把Array當(dāng)做是一個(gè)Container。

泛型的Where語句 (Generic Where Clauses)

泛型的where語句可以讓我們要求關(guān)聯(lián)類型遵循一個(gè)特定的協(xié)議,或者關(guān)聯(lián)類型和類型參數(shù)必須相同。

例如下面這個(gè)例子:

func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatalbe {
    // 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
}

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."

雖然ArrayStack是不同的類型,但是他們都遵循Container協(xié)議,并且都包含相同類型的值。

有泛型Where語句的擴(kuò)展 (Extensions with a Generic Where Clause)

例如下面這個(gè)例子:

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

如果沒有泛型where語句,將會(huì)出現(xiàn)一個(gè)問題:isTop(_:)方法使用了==運(yùn)算符,但是Stack沒有要求它的元素是可以比較的,所以使用==將會(huì)編譯錯(cuò)誤。

下面是isTop(_:)方法的使用:

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."

下面是使用where擴(kuò)展Container:

extension Container where Item: Equatalbe {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

startsWith(_:)適用于遵循了Container協(xié)議的類型:

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// Prints "Starts with something else."

上面對(duì)Container的擴(kuò)展要求Item遵循Equatable協(xié)議,我們還可以要求Item是一個(gè)具體的類型:

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"

我們還可以在where語句中可以添加多個(gè)約束條件,條件之間用逗號(hào)隔開即可。


第二十三部分完。下個(gè)部分:【Swift 3.1】24 -訪問權(quán)限


如果有錯(cuò)誤的地方,歡迎指正!謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 758評(píng)論 0 0
  • 本章將會(huì)介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴(kuò)展一個(gè)泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 713評(píng)論 0 2
  • 泛型代碼可以確保你寫出靈活的,可重用的函數(shù)和定義出任何你所確定好的需求的類型。你的可以寫出避免重復(fù)的代碼,并且用一...
    iOS_Developer閱讀 880評(píng)論 0 0
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出...
    無灃閱讀 1,656評(píng)論 0 4
  • 杜鵑花盛放一如彩云絢爛 空氣里流動(dòng)著紫丁香的夢(mèng) 風(fēng)信子的花冠過濾了時(shí)光 草叢茂密,鋪展一張綠色地毯 一個(gè)女人,一條...
    林嘉梓閱讀 530評(píng)論 12 39

友情鏈接更多精彩內(nèi)容