泛型 - Generics

2018-12-06 16:05 更新

泛型

泛型代碼可以讓你寫出根據(jù)自我需求定義、適用于任何類型的,靈活且可重用的函數(shù)和類型。它的可以讓你避免重復(fù)的代碼,用一種清晰和抽象的方式來(lái)表達(dá)代碼的意圖。

泛型是 Swift 強(qiáng)大特征中的其中一個(gè),許多 Swift 標(biāo)準(zhǔn)庫(kù)是通過泛型代碼構(gòu)建出來(lái)的。事實(shí)上,泛型的使用貫穿了整本語(yǔ)言手冊(cè),只是你沒有發(fā)現(xiàn)而已。例如,Swift 的數(shù)組和字典類型都是泛型集。你可以創(chuàng)建一個(gè)Int數(shù)組,也可創(chuàng)建一個(gè)String數(shù)組,或者甚至于可以是任何其他 Swift 的類型數(shù)據(jù)數(shù)組。同樣的,你也可以創(chuàng)建存儲(chǔ)任何指定類型的字典(dictionary),而且這些類型可以是沒有限制的。

泛型所解決的問題

這里是一個(gè)標(biāo)準(zhǔn)的,非泛型函數(shù)swapTwoInts,用來(lái)交換兩個(gè)Int值:

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

這個(gè)函數(shù)使用寫入讀出(in-out)參數(shù)來(lái)交換ab的值,請(qǐng)參考寫入讀出參數(shù)。

swapTwoInts函數(shù)可以交換b的原始值到a,也可以交換a的原始值到b,你可以調(diào)用這個(gè)函數(shù)交換兩個(gè)Int變量值:

    var someInt = 3
    var anotherInt = 107
    swapTwoInts(&someInt, &anotherInt)
    println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
    // 輸出 "someInt is now 107, and anotherInt is now 3"

swapTwoInts函數(shù)是非常有用的,但是它只能交換Int值,如果你想要交換兩個(gè)String或者Double,就不得不寫更多的函數(shù),如 swapTwoStringsswapTwoDoublesfunctions,如同如下所示:

    func swapTwoStrings(inout a: String, inout b: String) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    func swapTwoDoubles(inout a: Double, inout b: Double) {
        let temporaryA = a
        a = b
        b = temporaryA
    }

你可能注意到 swapTwoInts、 swapTwoStringsswapTwoDoubles函數(shù)功能都是相同的,唯一不同之處就在于傳入的變量類型不同,分別是Int、StringDouble

但實(shí)際應(yīng)用中通常需要一個(gè)用處更強(qiáng)大并且盡可能的考慮到更多的靈活性單個(gè)函數(shù),可以用來(lái)交換兩個(gè)任何類型值,很幸運(yùn)的是,泛型代碼幫你解決了這種問題。(一個(gè)這種泛型函數(shù)后面已經(jīng)定義好了。)

注意:
在所有三個(gè)函數(shù)中,ab的類型是一樣的。如果ab不是相同的類型,那它們倆就不能互換值。Swift 是類型安全的語(yǔ)言,所以它不允許一個(gè)String類型的變量和一個(gè)Double類型的變量互相交換值。如果一定要做,Swift 將報(bào)編譯錯(cuò)誤。

泛型函數(shù)

泛型函數(shù)可以工作于任何類型,這里是一個(gè)上面swapTwoInts函數(shù)的泛型版本,用于交換兩個(gè)值:

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

swapTwoValues函數(shù)主體和swapTwoInts函數(shù)是一樣的,它只在第一行稍微有那么一點(diǎn)點(diǎn)不同于swapTwoInts,如下所示:

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

這個(gè)函數(shù)的泛型版本使用了占位類型名字(通常此情況下用字母T來(lái)表示)來(lái)代替實(shí)際類型名(如IntStringDouble)。占位類型名沒有提示T必須是什么類型,但是它提示了ab必須是同一類型T,而不管T表示什么類型。只有swapTwoValues函數(shù)在每次調(diào)用時(shí)所傳入的實(shí)際類型才能決定T所代表的類型。

另外一個(gè)不同之處在于這個(gè)泛型函數(shù)名后面跟著的占位類型名字(T)是用尖括號(hào)括起來(lái)的(<T>)。這個(gè)尖括號(hào)告訴 Swift 那個(gè)TswapTwoValues函數(shù)所定義的一個(gè)類型。因?yàn)?code>T是一個(gè)占位命名類型,Swift 不會(huì)去查找命名為T的實(shí)際類型。

swapTwoValues函數(shù)除了要求傳入的兩個(gè)任何類型值是同一類型外,也可以作為swapTwoInts函數(shù)被調(diào)用。每次swapTwoValues被調(diào)用,T所代表的類型值都會(huì)傳給函數(shù)。

在下面的兩個(gè)例子中,T分別代表IntString

    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ù)swapTwoValues是受swap函數(shù)啟發(fā)而實(shí)現(xiàn)的。swap函數(shù)存在于 Swift 標(biāo)準(zhǔn)庫(kù),并可以在其它類中任意使用。如果你在自己代碼中需要類似swapTwoValues函數(shù)的功能,你可以使用已存在的交換函數(shù)swap函數(shù)。

類型參數(shù)

在上面的swapTwoValues例子中,占位類型T是一種類型參數(shù)的示例。類型參數(shù)指定并命名為一個(gè)占位類型,并且緊隨在函數(shù)名后面,使用一對(duì)尖括號(hào)括起來(lái)(如<T>)。

一旦一個(gè)類型參數(shù)被指定,那么其可以被使用來(lái)定義一個(gè)函數(shù)的參數(shù)類型(如swapTwoValues函數(shù)中的參數(shù)ab),或作為一個(gè)函數(shù)返回類型,或用作函數(shù)主體中的注釋類型。在這種情況下,被類型參數(shù)所代表的占位類型不管函數(shù)任何時(shí)候被調(diào)用,都會(huì)被實(shí)際類型所替換(在上面swapTwoValues例子中,當(dāng)函數(shù)第一次被調(diào)用時(shí),TInt替換,第二次調(diào)用時(shí),被String替換。)。

你可支持多個(gè)類型參數(shù),命名在尖括號(hào)中,用逗號(hào)分開。

命名類型參數(shù)

在簡(jiǎn)單的情況下,泛型函數(shù)或泛型類型需要指定一個(gè)占位類型(如上面的swapTwoValues泛型函數(shù),或一個(gè)存儲(chǔ)單一類型的泛型集,如數(shù)組),通常用一單個(gè)字母T來(lái)命名類型參數(shù)。不過,你可以使用任何有效的標(biāo)識(shí)符來(lái)作為類型參數(shù)名。

如果你使用多個(gè)參數(shù)定義更復(fù)雜的泛型函數(shù)或泛型類型,那么使用更多的描述類型參數(shù)是非常有用的。例如,Swift 字典(Dictionary)類型有兩個(gè)類型參數(shù),一個(gè)是鍵,另外一個(gè)是值。如果你自己寫字典,你或許會(huì)定義這兩個(gè)類型參數(shù)為KeyTypeValueType,用來(lái)記住它們?cè)谀愕姆盒痛a中的作用。

注意
請(qǐng)始終使用大寫字母開頭的駝峰式命名法(例如TKeyType)來(lái)給類型參數(shù)命名,以表明它們是類型的占位符,而非類型值。

泛型類型

通常在泛型函數(shù)中,Swift 允許你定義你自己的泛型類型。這些自定義類、結(jié)構(gòu)體和枚舉作用于任何類型,如同ArrayDictionary的用法。

這部分向你展示如何寫一個(gè)泛型集類型--Stack(棧)。一個(gè)棧是一系列值域的集合,和Array(數(shù)組)類似,但其是一個(gè)比 Swift 的Array類型更多限制的集合。一個(gè)數(shù)組可以允許其里面任何位置的插入/刪除操作,而棧,只允許在集合的末端添加新的項(xiàng)(如同push一個(gè)新值進(jìn)棧)。同樣的一個(gè)棧也只能從末端移除項(xiàng)(如同pop一個(gè)值出棧)。

注意
棧的概念已被UINavigationController類使用來(lái)模擬試圖控制器的導(dǎo)航結(jié)構(gòu)。你通過調(diào)用UINavigationControllerpushViewController:animated:方法來(lái)為導(dǎo)航棧添加(add)新的試圖控制器;而通過popViewControllerAnimated:的方法來(lái)從導(dǎo)航棧中移除(pop)某個(gè)試圖控制器。每當(dāng)你需要一個(gè)嚴(yán)格的后進(jìn)先出方式來(lái)管理集合,堆棧都是最實(shí)用的模型。

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

Image of Generics_1.png

  1. 現(xiàn)在有三個(gè)值在棧中;
  2. 第四個(gè)值“pushed”到棧的頂部;
  3. 現(xiàn)在有四個(gè)值在棧中,最近的那個(gè)在頂部;
  4. 棧中最頂部的那個(gè)項(xiàng)被移除,或稱之為“popped”;
  5. 移除掉一個(gè)值后,現(xiàn)在棧又重新只有三個(gè)值。

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

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

這個(gè)結(jié)構(gòu)體在棧中使用一個(gè)Array性質(zhì)的items存儲(chǔ)值。Stack提供兩個(gè)方法:pushpop,從棧中壓進(jìn)一個(gè)值和移除一個(gè)值。這些方法標(biāo)記為可變的,因?yàn)樗鼈冃枰薷模ɑ?em>轉(zhuǎn)換)結(jié)構(gòu)體的items數(shù)組。

上面所展現(xiàn)的IntStack類型只能用于Int值,不過,其對(duì)于定義一個(gè)泛型Stack類(可以處理任何類型值的棧)是非常有用的。

這里是一個(gè)相同代碼的泛型版本:

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

注意到Stack的泛型版本基本上和非泛型版本相同,但是泛型版本的占位類型參數(shù)為T代替了實(shí)際Int類型。這種類型參數(shù)包含在一對(duì)尖括號(hào)里(<T>),緊隨在結(jié)構(gòu)體名字后面。

T定義了一個(gè)名為“某種類型T”的節(jié)點(diǎn)提供給后來(lái)用。這種將來(lái)類型可以在結(jié)構(gòu)體的定義里任何地方表示為“T”。在這種情況下,T在如下三個(gè)地方被用作節(jié)點(diǎn):

  • 創(chuàng)建一個(gè)名為items的屬性,使用空的T類型值數(shù)組對(duì)其進(jìn)行初始化;
  • 指定一個(gè)包含一個(gè)參數(shù)名為itempush方法,該參數(shù)必須是T類型;
  • 指定一個(gè)pop方法的返回值,該返回值將是一個(gè)T類型值。

當(dāng)創(chuàng)建一個(gè)新單例并初始化時(shí), 通過用一對(duì)緊隨在類型名后的尖括號(hào)里寫出實(shí)際指定棧用到類型,創(chuàng)建一個(gè)Stack實(shí)例,同創(chuàng)建ArrayDictionary一樣:

    var stackOfStrings = Stack<String>()
    stackOfStrings.push("uno")
    stackOfStrings.push("dos")
    stackOfStrings.push("tres")
    stackOfStrings.push("cuatro")
    // 現(xiàn)在棧已經(jīng)有4個(gè)string了

下圖將展示stackOfStrings如何push這四個(gè)值進(jìn)棧的過程:

Image of Generics_2.png

從棧中pop并移除值"cuatro":

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

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

Image of Generics_3.png

由于Stack是泛型類型,所以在 Swift 中其可以用來(lái)創(chuàng)建任何有效類型的棧,這種方式如同ArrayDictionary。

類型約束

swapTwoValues函數(shù)和Stack類型可以作用于任何類型,不過,有的時(shí)候?qū)κ褂迷诜盒秃瘮?shù)和泛型類型上的類型強(qiáng)制約束為某種特定類型是非常有用的。類型約束指定了一個(gè)必須繼承自指定類的類型參數(shù),或者遵循一個(gè)特定的協(xié)議或協(xié)議構(gòu)成。

例如,Swift 的Dictionary類型對(duì)作用于其鍵的類型做了些限制。在字典的描述中,字典的鍵類型必須是可哈希,也就是說(shuō),必須有一種方法可以使其被唯一的表示。Dictionary之所以需要其鍵是可哈希是為了以便于其檢查其是否已經(jīng)包含某個(gè)特定鍵的值。如無(wú)此需求,Dictionary既不會(huì)告訴是否插入或者替換了某個(gè)特定鍵的值,也不能查找到已經(jīng)存儲(chǔ)在字典里面的給定鍵值。

這個(gè)需求強(qiáng)制加上一個(gè)類型約束作用于Dictionary的鍵上,當(dāng)然其鍵類型必須遵循Hashable協(xié)議(Swift 標(biāo)準(zhǔn)庫(kù)中定義的一個(gè)特定協(xié)議)。所有的 Swift 基本類型(如String,Int, DoubleBool)默認(rèn)都是可哈希。

當(dāng)你創(chuàng)建自定義泛型類型時(shí),你可以定義你自己的類型約束,當(dāng)然,這些約束要支持泛型編程的強(qiáng)力特征中的多數(shù)。抽象概念如可哈希具有的類型特征是根據(jù)它們概念特征來(lái)界定的,而不是它們的直接類型特征。

類型約束語(yǔ)法

你可以寫一個(gè)在一個(gè)類型參數(shù)名后面的類型約束,通過冒號(hào)分割,來(lái)作為類型參數(shù)鏈的一部分。這種作用于泛型函數(shù)的類型約束的基礎(chǔ)語(yǔ)法如下所示(和泛型類型的語(yǔ)法相同):

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

上面這個(gè)假定函數(shù)有兩個(gè)類型參數(shù)。第一個(gè)類型參數(shù)T,有一個(gè)需要T必須是SomeClass子類的類型約束;第二個(gè)類型參數(shù)U,有一個(gè)需要U必須遵循SomeProtocol協(xié)議的類型約束。

類型約束行為

這里有個(gè)名為findStringIndex的非泛型函數(shù),該函數(shù)功能是去查找包含一給定String值的數(shù)組。若查找到匹配的字符串,findStringIndex函數(shù)返回該字符串在數(shù)組中的索引值(Int),反之則返回nil

    func findStringIndex(array: [String], valueToFind: String) -> Int? {
        for (index, value) in enumerate(array) {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }

findStringIndex函數(shù)可以作用于查找一字符串?dāng)?shù)組中的某個(gè)字符串:

    let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
    if let foundIndex = findStringIndex(strings, "llama") {
        println("The index of llama is \(foundIndex)")
    }
    // 輸出 "The index of llama is 2"

如果只是針對(duì)字符串而言查找在數(shù)組中的某個(gè)值的索引,用處不是很大,不過,你可以寫出相同功能的泛型函數(shù)findIndex,用某個(gè)類型T值替換掉提到的字符串。

這里展示如何寫一個(gè)你或許期望的findStringIndex的泛型版本findIndex。請(qǐng)注意這個(gè)函數(shù)仍然返回Int,是不是有點(diǎn)迷惑呢,而不是泛型類型?那是因?yàn)楹瘮?shù)返回的是一個(gè)可選的索引數(shù),而不是從數(shù)組中得到的一個(gè)可選值。需要提醒的是,這個(gè)函數(shù)不會(huì)編譯,原因在例子后面會(huì)說(shuō)明:

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

上面所寫的函數(shù)不會(huì)編譯。這個(gè)問題的位置在等式的檢查上,“if value == valueToFind”。不是所有的 Swift 中的類型都可以用等式符(==)進(jìn)行比較。例如,如果你創(chuàng)建一個(gè)你自己的類或結(jié)構(gòu)體來(lái)表示一個(gè)復(fù)雜的數(shù)據(jù)模型,那么 Swift 沒法猜到對(duì)于這個(gè)類或結(jié)構(gòu)體而言“等于”的意思。正因如此,這部分代碼不能可能保證工作于每個(gè)可能的類型T,當(dāng)你試圖編譯這部分代碼時(shí)估計(jì)會(huì)出現(xiàn)相應(yīng)的錯(cuò)誤。

不過,所有的這些并不會(huì)讓我們無(wú)從下手。Swift 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè)Equatable協(xié)議,該協(xié)議要求任何遵循的類型實(shí)現(xiàn)等式符(==)和不等符(!=)對(duì)任何兩個(gè)該類型進(jìn)行比較。所有的 Swift 標(biāo)準(zhǔn)類型自動(dòng)支持Equatable協(xié)議。

任何Equatable類型都可以安全的使用在findIndex函數(shù)中,因?yàn)槠浔WC支持等式操作。為了說(shuō)明這個(gè)事實(shí),當(dāng)你定義一個(gè)函數(shù)時(shí),你可以寫一個(gè)Equatable類型約束作為類型參數(shù)定義的一部分:

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

findIndex中這個(gè)單個(gè)類型參數(shù)寫做:T: Equatable,也就意味著“任何T類型都遵循Equatable協(xié)議”。

findIndex函數(shù)現(xiàn)在則可以成功的編譯過,并且作用于任何遵循Equatable的類型,如DoubleString:

    let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
    // doubleIndex is an optional Int with no value, because 9.3 is not in the array
    let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
    // stringIndex is an optional Int containing a value of 2

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

當(dāng)定義一個(gè)協(xié)議時(shí),有的時(shí)候聲明一個(gè)或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分是非常有用的。一個(gè)關(guān)聯(lián)類型作為協(xié)議的一部分,給定了類型的一個(gè)占位名(或別名)。作用于關(guān)聯(lián)類型上實(shí)際類型在協(xié)議被實(shí)現(xiàn)前是不需要指定的。關(guān)聯(lián)類型被指定為typealias關(guān)鍵字。

關(guān)聯(lián)類型行為

這里是一個(gè)Container協(xié)議的例子,定義了一個(gè)ItemType關(guān)聯(lián)類型:

    protocol Container {
        typealias ItemType
        mutating func append(item: ItemType)
        var count: Int { get }
        subscript(i: Int) -> ItemType { get }
    }

Container協(xié)議定義了三個(gè)任何容器必須支持的兼容要求:

  • 必須可能通過append方法添加一個(gè)新item到容器里;
  • 必須可能通過使用count屬性獲取容器里items的數(shù)量,并返回一個(gè)Int值;
  • 必須可能通過容器的Int索引值下標(biāo)可以檢索到每一個(gè)item。

這個(gè)協(xié)議沒有指定容器里item是如何存儲(chǔ)的或何種類型是允許的。這個(gè)協(xié)議只指定三個(gè)任何遵循Container類型所必須支持的功能點(diǎn)。一個(gè)遵循的類型在滿足這三個(gè)條件的情況下也可以提供其他額外的功能。

任何遵循Container協(xié)議的類型必須指定存儲(chǔ)在其里面的值類型,必須保證只有正確類型的items可以加進(jìn)容器里,必須明確可以通過其下標(biāo)返回item類型。

為了定義這三個(gè)條件,Container協(xié)議需要一個(gè)方法指定容器里的元素將會(huì)保留,而不需要知道特定容器的類型。Container協(xié)議需要指定任何通過append方法添加到容器里的值和容器里元素是相同類型,并且通過容器下標(biāo)返回的容器元素類型的值的類型是相同類型。

為了達(dá)到此目的,Container協(xié)議聲明了一個(gè)ItemType的關(guān)聯(lián)類型,寫作typealias ItemType。這個(gè)協(xié)議不會(huì)定義ItemType是什么的別名,這個(gè)信息將由任何遵循協(xié)議的類型來(lái)提供。盡管如此,ItemType別名提供了一種識(shí)別Container中Items類型的方法,并且用于append方法和subscript方法的類型定義,以便保證任何Container期望的行為能夠被執(zhí)行。

這里是一個(gè)早前IntStack類型的非泛型版本,遵循Container協(xié)議:

    struct IntStack: Container {
        // IntStack的原始實(shí)現(xiàn)
        var items = [Int]()
        mutating func push(item: Int) {
            items.append(item)
        }
        mutating func pop() -> Int {
            return items.removeLast()
        }
        // 遵循Container協(xié)議的實(shí)現(xiàn)
        typealias ItemType = Int
        mutating func append(item: Int) {
            self.push(item)
        }
        var count: Int {
        return items.count
        }
        subscript(i: Int) -> Int {
            return items[i]
        }
    }

IntStack類型實(shí)現(xiàn)了Container協(xié)議的所有三個(gè)要求,在IntStack類型的每個(gè)包含部分的功能都滿足這些要求。

此外,IntStack指定了Container的實(shí)現(xiàn),適用的ItemType被用作Int類型。對(duì)于這個(gè)Container協(xié)議實(shí)現(xiàn)而言,定義 typealias ItemType = Int,將抽象的ItemType類型轉(zhuǎn)換為具體的Int類型。

感謝Swift類型參考,你不用在IntStack定義部分聲明一個(gè)具體的IntItemType。由于IntStack遵循Container協(xié)議的所有要求,只要通過簡(jiǎn)單的查找append方法的item參數(shù)類型和下標(biāo)返回的類型,Swift就可以推斷出合適的ItemType來(lái)使用。確實(shí),如果上面的代碼中你刪除了 typealias ItemType = Int這一行,一切仍舊可以工作,因?yàn)樗宄闹繧temType使用的是何種類型。

你也可以生成遵循Container協(xié)議的泛型Stack類型:

    struct Stack<T>: Container {
        // original Stack<T> implementation
        var items = [T]()
        mutating func push(item: T) {
            items.append(item)
        }
        mutating func pop() -> T {
            return items.removeLast()
        }
        // conformance to the Container protocol
        mutating func append(item: T) {
            self.push(item)
        }
        var count: Int {
        return items.count
        }
        subscript(i: Int) -> T {
            return items[i]
        }
    }

這個(gè)時(shí)候,占位類型參數(shù)T被用作append方法的item參數(shù)和下標(biāo)的返回類型。Swift 因此可以推斷出被用作這個(gè)特定容器的ItemTypeT的合適類型。

擴(kuò)展一個(gè)存在的類型為一指定關(guān)聯(lián)類型

使用擴(kuò)展來(lái)添加協(xié)議兼容性中有描述擴(kuò)展一個(gè)存在的類型添加遵循一個(gè)協(xié)議。這個(gè)類型包含一個(gè)關(guān)聯(lián)類型的協(xié)議。

Swift的Array已經(jīng)提供append方法,一個(gè)count屬性和通過下標(biāo)來(lái)查找一個(gè)自己的元素。這三個(gè)功能都達(dá)到Container協(xié)議的要求。也就意味著你可以擴(kuò)展Array去遵循Container協(xié)議,只要通過簡(jiǎn)單聲明Array適用于該協(xié)議而已。如何實(shí)踐這樣一個(gè)空擴(kuò)展,在使用擴(kuò)展來(lái)聲明協(xié)議的采納中有描述這樣一個(gè)實(shí)現(xiàn)一個(gè)空擴(kuò)展的行為:

    extension Array: Container {}

如同上面的泛型Stack類型一樣,Array的append方法和下標(biāo)保證Swift可以推斷出ItemType所使用的適用的類型。定義了這個(gè)擴(kuò)展后,你可以將任何Array當(dāng)作Container來(lái)使用。

Where 語(yǔ)句

類型約束能夠確保類型符合泛型函數(shù)或類的定義約束。

對(duì)關(guān)聯(lián)類型定義約束是非常有用的。你可以在參數(shù)列表中通過where語(yǔ)句定義參數(shù)的約束。一個(gè)where語(yǔ)句能夠使一個(gè)關(guān)聯(lián)類型遵循一個(gè)特定的協(xié)議,以及(或)那個(gè)特定的類型參數(shù)和關(guān)聯(lián)類型可以是相同的。你可以寫一個(gè)where語(yǔ)句,緊跟在在類型參數(shù)列表后面,where語(yǔ)句后跟一個(gè)或者多個(gè)針對(duì)關(guān)聯(lián)類型的約束,以及(或)一個(gè)或多個(gè)類型和關(guān)聯(lián)類型間的等價(jià)(equality)關(guān)系。

下面的例子定義了一個(gè)名為allItemsMatch的泛型函數(shù),用來(lái)檢查兩個(gè)Container實(shí)例是否包含相同順序的相同元素。如果所有的元素能夠匹配,那么返回一個(gè)為trueBoolean值,反之則為false。

被檢查的兩個(gè)Container可以不是相同類型的容器(雖然它們可以是),但它們確實(shí)擁有相同類型的元素。這個(gè)需求通過一個(gè)類型約束和where語(yǔ)句結(jié)合來(lái)表示:

    func allItemsMatch<
        C1: Container, C2: Container
        where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
        (someContainer: C1, anotherContainer: C2) -> Bool {

            // 檢查兩個(gè)Container的元素個(gè)數(shù)是否相同
            if someContainer.count != anotherContainer.count {
                return false
            }

        // 檢查兩個(gè)Container相應(yīng)位置的元素彼此是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }

        // 如果所有元素檢查都相同則返回true
        return true

    }

這個(gè)函數(shù)用了兩個(gè)參數(shù):someContaineranotherContainer。someContainer參數(shù)是類型C1,anotherContainer參數(shù)是類型C2。C1C2是容器的兩個(gè)占位類型參數(shù),決定了這個(gè)函數(shù)何時(shí)被調(diào)用。

這個(gè)函數(shù)的類型參數(shù)列緊隨在兩個(gè)類型參數(shù)需求的后面:

  • C1必須遵循Container協(xié)議 (寫作 C1: Container)。
  • C2必須遵循Container協(xié)議 (寫作 C2: Container)。
  • C1ItemType同樣是C2的ItemType(寫作 C1.ItemType == C2.ItemType)。
  • C1ItemType必須遵循Equatable協(xié)議 (寫作 C1.ItemType: Equatable)。

第三個(gè)和第四個(gè)要求被定義為一個(gè)where語(yǔ)句的一部分,寫在關(guān)鍵字where后面,作為函數(shù)類型參數(shù)鏈的一部分。

這些要求意思是:

someContainer是一個(gè)C1類型的容器。anotherContainer是一個(gè)C2類型的容器。someContaineranotherContainer包含相同的元素類型。someContainer中的元素可以通過不等于操作(!=)來(lái)檢查它們是否彼此不同。

第三個(gè)和第四個(gè)要求結(jié)合起來(lái)的意思是anotherContainer中的元素也可以通過 != 操作來(lái)檢查,因?yàn)樗鼈冊(cè)?code>someContainer中元素確實(shí)是相同的類型。

這些要求能夠使allItemsMatch函數(shù)比較兩個(gè)容器,即便它們是不同的容器類型。

allItemsMatch首先檢查兩個(gè)容器是否擁有同樣數(shù)目的items,如果它們的元素?cái)?shù)目不同,沒有辦法進(jìn)行匹配,函數(shù)就會(huì)false。

檢查完之后,函數(shù)通過for-in循環(huán)和半閉區(qū)間操作(..)來(lái)迭代someContainer中的所有元素。對(duì)于每個(gè)元素,函數(shù)檢查是否someContainer中的元素不等于對(duì)應(yīng)的anotherContainer中的元素,如果這兩個(gè)元素不等,則這兩個(gè)容器不匹配,返回false。

如果循環(huán)體結(jié)束后未發(fā)現(xiàn)沒有任何的不匹配,那表明兩個(gè)容器匹配,函數(shù)返回true

這里演示了allItemsMatch函數(shù)運(yùn)算的過程:

    var stackOfStrings = Stack<String>()
    stackOfStrings.push("uno")
    stackOfStrings.push("dos")
    stackOfStrings.push("tres")

    var arrayOfStrings = ["uno", "dos", "tres"]

    if allItemsMatch(stackOfStrings, arrayOfStrings) {
        println("All items match.")
    } else {
        println("Not all items match.")
    }
    // 輸出 "All items match."

上面的例子創(chuàng)建一個(gè)Stack單例來(lái)存儲(chǔ)String,然后壓了三個(gè)字符串進(jìn)棧。這個(gè)例子也創(chuàng)建了一個(gè)Array單例,并初始化包含三個(gè)同棧里一樣的原始字符串。即便棧和數(shù)組是不同的類型,但它們都遵循Container協(xié)議,而且它們都包含同樣的類型值。因此你可以調(diào)用allItemsMatch函數(shù),用這兩個(gè)容器作為它的參數(shù)。在上面的例子中,allItemsMatch函數(shù)正確的顯示了所有的這兩個(gè)容器的items匹配。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)