swift簡單總結(三十三)—— 泛型

版本記錄

版本號 時間
V1.0 2017.08.01

前言

我是swift2.0的時候開始接觸的,記得那時候還不是很穩定,公司的項目也都是用oc做的,并不對swift很重視,我自己學了一段時間,到現在swift3.0+已經出來了,自己平時也不寫,忘記的也差不多了,正好項目這段時間已經上線了,不是很忙,我就可以每天總結一點了,希望對自己對大家有所幫助。在總結的時候我會對比oc進行說明,有代碼的我會給出相關比對代碼。
1. swift簡單總結(一)—— 數據簡單值和類型轉換
2. swift簡單總結(二)—— 簡單值和控制流
3. swift簡單總結(三)—— 循環控制和函數
4. swift簡單總結(四)—— 函數和類
5. swift簡單總結(五)—— 枚舉和結構體
6. swift簡單總結(六)—— 協議擴展與泛型
7. swift簡單總結(七)—— 數據類型
8. swift簡單總結(八)—— 別名、布爾值與元組
9. swift簡單總結(九)—— 可選值和斷言
10. swift簡單總結(十)—— 運算符
11. swift簡單總結(十一)—— 字符串和字符
12. swift簡單總結(十二)—— 集合類型之數組
13. swift簡單總結(十三)—— 集合類型之字典
14. swift簡單總結(十四)—— 控制流
15. swift簡單總結(十五)—— 控制轉移語句
16. swift簡單總結(十六)—— 函數
17. swift簡單總結(十七)—— 閉包(Closures)
18. swift簡單總結(十八)—— 枚舉
19. swift簡單總結(十九)—— 類和結構體
20. swift簡單總結(二十)—— 屬性
21. swift簡單總結(二十一)—— 方法
22. swift簡單總結(二十二)—— 下標腳本
23. swift簡單總結(二十三)—— 繼承
24. swift簡單總結(二十四)—— 構造過程
25. swift簡單總結(二十五)—— 構造過程
26. swift簡單總結(二十六)—— 析構過程
27. swift簡單總結(二十七)—— 自動引用計數
28. swift簡單總結(二十八)—— 可選鏈
29. swift簡單總結(二十九)—— 類型轉換
30.swift簡單總結(三十)—— 嵌套類型
31.swift簡單總結(三十一)—— 擴展
32.swift簡單總結(三十二)—— 協議

泛型

泛型是swift強大特性中的其中一個,例如:swift數組和字典類型都是泛型集,你可以創建一個Int數組,也可創建一個String數組或者甚至于可以是任何其他swift的類型數據數組。

下面主要從幾點進行講述:

  • 泛型所解決的問題
  • 泛型函數
  • 類型參數
  • 命名類型參數
  • 泛型類型
  • 類型約束
  • 關聯類型
  • where語句

泛型所解決的問題

先看一個例子,標準的非泛型函數,用來交換交換Int的值。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var someInt = 3
        var anotherInt = 107
        swapTwoInts(a: &someInt, b: &anotherInt)
        print("someInt : \(someInt), anotherInt : \(anotherInt)")
    }
    
    func swapTwoInts(a : inout Int, b : inout Int){
        let temp = a
        a = b
        b = temp
    }
}

下面看輸出結果

someInt : 107, anotherInt : 3

swapTwoInts函數是非常有用的,但是它只能交換Int的值,如果你想要交換兩個DoubleString值,就不得不寫更多的函數了,例如swapTwoDoublesswapTwoString

但是,在實際應用中,通常需要一個用處更強大并且盡可能的考慮更多的靈活性的單個函數,可以用來交換兩個任意類型值,這里泛型就能幫你解決這個問題。

注意swift中不同類型的值不能互換,所以abtemp必須是同類型的,否則交換會報錯。


泛型函數

泛型函數可以工作與任何類型,這里是一個swapTwoInts函數的泛型版本,用于交換兩個值。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var someInt = 3
        var anotherInt = 107
        swapTwoValue(a: &someInt, b: &anotherInt)
        print("someInt : \(someInt), anotherInt : \(anotherInt)")
        
        var someString = " hello "
        var anotherString = " world "
        swapTwoValue(a: &someString, b: &anotherString)
        print("someString : \(someString), anotherString : \(anotherString)")
    }
    
    func swapTwoValue <T>(a : inout T, b : inout T){
        let temp = a
        a = b
        b = temp
    }
}

下面看輸出結果

someInt : 107, anotherInt : 3
someString :  world , anotherString :  hello 

上面利用泛型,可以交換任意兩個相同類型的值。這里T是一個占位命名類型,swift不會查找命名為T的實際類型。


類型參數

在上面的例子swapTwoValue中,占位類型T是一種類型參數的示例,類型參數指定并命名為一個占位類型,并且緊隨在函數名后面,使用一對尖括號括起來<T>

你可支持多個類型參數,命名在尖括號中,用逗號分開。


命名類型參數

在簡答的情況下,泛型函數或泛型類型需要指定一個占位類型,通常用一個單個字母T命名類型參數,不過,你可以使用任何有效的標識符來作為類型參數名。

如果你使用多個參數定義更復雜的泛型函數或泛型類型,那么使用更多的描述類型參數是非常有用的,例如,swift字典dictionary類型就有兩個類型參數,一個是鍵,另外一個就是值。

請始終使用大寫字母開頭的駝峰命名法,例如TKeyType來給類型參數命名,以表明它們是類型的占位符,而非類型值。


泛型類型

在泛型函數中,swift允許你定義自己的泛型類型,這些自定義類、結構體和枚舉作用與任何類型。

下面展示的是泛型集類型,stack棧。

棧模型

有棧就有入棧和出棧等操作,如上圖展示。

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

上面的結構體就展示的是入棧和出棧等操作。上面的只限于整型值,下面我們對其進行擴展,以適應各種類型。

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

下面調用一下

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var stackOfString = Stack<String>()
        stackOfString.push(item: "uno")
        stackOfString.push(item: "dos")
        stackOfString.push(item: "tres")
        stackOfString.push(item: "cuatro")
        print(stackOfString)
    }
    
}

下面看輸出結果

Stack<String>(items: ["uno", "dos", "tres", "cuatro"])

下面看一下入棧示意圖。

入棧示意圖

下面我們在看一下出棧

stackOfString.pop()

看一下輸出結果

Stack<String>(items: ["uno", "dos", "tres"])

下面看一下出棧示意圖。

出棧示意圖

類型約束

雖然前面的都是定義任何類型的泛型,但是有時候強制限制類型是很有用的,類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。比如:swift字典的鍵類型就進行了約束,一定要是可哈希的,鍵必須遵循哈希協議Hashable,所有swift基本類型如IntStringDoubleBool都是可哈希的。

1. 類型約束語法

你可以寫一個在一個類型參數名后面的類型約束,通過冒號分割,來作為類型參數鏈的一部分,這種作用于泛型函數的類型約束的基礎語法如下所示。

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

上面語法實例匯總,有類型參數,一個是T,有一個需要T必須是SomeClass子類的類型約束;第二個類型參數U,有一個需要U遵循SomeProtcol協議的類型約束。

2. 類型約束行為

先看一個簡單例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        let stringArr = ["cat", "dog", "llama", "parakeet", "terrapin"]
        if let foundIndex = findStringIndex(array: stringArr, valueToFind: "llama") {
            print("The index of llama is \(foundIndex)")
        }
    }
    
    func findStringIndex(array : [String], valueToFind : String) -> Int?{
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
}

下面是輸出結果

The index of llama is 2

上面這個實例的功能就是查找數組中指定元素額的下標,這里只是針對字符串而言有效,其實我們還可以擴展到指定類型有效。

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

上面就是擴展后的實例,但是這個不會編譯通過,報錯。問題出在if value == valueToFind上面,不是所有的swift中的類型都可以用等式符==進行比較,比如,你自己創建一個你自己的類或結構體來表示一個復雜的數據模型,但是swift沒法猜到對于這個類或結構體而言“等于”的意思,正因如此,這部分代碼不能保證工作于每個可能的類型T

不過,這個可以解決,swift標準庫中定義了一個Equatable協議,該協議要求任何遵循的類型實現等式符==和不等式符!=對任何兩個該類型進行比較,所有的swift標準類型自動支持Equatable協議。

下面我們改進一下代碼。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        let stringArr = ["cat", "dog", "llama", "parakeet", "terrapin"]
        let stringResult = findIndex(array: stringArr, valueToFind: "dog")
        print("stringResult \(stringResult)")
        
        let doubleArr = [3.14, 4.34, 1.00, 252.9]
        let doubleResult = findIndex(array: doubleArr, valueToFind: 3.14)
        print("doubleResult \(doubleResult)")
    }
    
    func findIndex<T : Equatable>(array : [T], valueToFind : T) -> Int?{
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
}

下面看一下輸出結果

stringResult Optional(1)
doubleResult Optional(0)

這里,findIndex<T : Equatable>意味著“任何T類型都遵循Equatable協議”。


關聯類型 - associatedtype

當定義一個協議時,有時候需要聲明一個或多個關聯類型作為協議定義的一部分是非常有用的,一個關聯類型作為協議的一部分,給定了類型的一個占位名(或別名),作用于關聯類型上實際類型在協議被實現前是不需要指定的,關聯類型被指定為associatedtype關鍵字。

1. 關聯類型行為

下面是一個簡單例子。

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

Container協議定義了三個任何容器必須支持的兼容要求

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

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

任何遵循Container協議的類型必須指定存儲在其里面的值類型,必須保證只有正確類型的items可以加進容器里面,必須明確可以通過其下標返回item類型。

為此,定義了ItemType的關聯類型,寫作associatedtype,這個協議不會定義ItemType是什么別名,這個信息將由任何遵循協議的類型來提供。

下面看一個簡單例子。

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

上面就是將"Int"和ItemType進行關聯,下面我們就看一下泛型在這里的使用。

struct Stack<T> : Container{
    var items = [T]()
    mutating func push(item : T){
        items.append(item)
    }
    mutating func pop() -> T{
        return items.removeLast()
    }
    
    //遵循Container協議的實現
    mutating func append(item: T) {
        self.push(item: item)
    }
    var count: Int{
        return items.count
    }
    subscript(i : Int) -> T{
        return items[i]
    }
}

2. 擴展一個存在的類型為一指定關聯類型

前面定義了Container協議,意味著你可以擴展Array去遵循Container協議。只需要簡單的聲明Array適用于該協議而已。下面定義的就是實現一個空擴展行為。

extension Array : Container{

}

Where語句

對關聯類型定義約束是非常有有用的,你可以在參數列表中通過where語句定義參數的約束,一個where語句能夠使一個關聯類型遵循一個特定的協議,以及那個特定的類型參數和關聯類型可以是相同的,可以寫一個where語句,緊跟在類型參數列表后面,where語句后跟一個或者多個針對關聯類型的約束,以及一個或多個類型和關聯類型間的等價關系。

看下面這個簡單例子。

    func allItemsMatch<C1 : Container, C2 : Container where C1.ItemType == C2.ItemType, C1.ItemType : Equatable>(someContainer : C1, anotherContainer : C2) -> Bool{
    
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        for i in 0...someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        return true
    }

這個函數的作用就是檢查兩個Container實例是否包含相同順序的相同元素。這個函數的類型參數緊隨在兩個類型參數需求的后面。

  • C1必須遵循Container協議
  • C2必須遵循Container協議
  • C1ItemType同樣也是C2ItemType
  • C1ItemType必須遵守Equatable協議。

后記

未完,待續~~~

布加迪
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • 本章將會介紹 泛型所解決的問題泛型函數類型參數命名類型參數泛型類型擴展一個泛型類型類型約束關聯類型泛型 Where...
    寒橋閱讀 646評論 0 2
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設置的要求的,靈活且可重用的 函數和類型。泛型...
    果啤閱讀 687評論 0 0
  • 泛型代碼可以確保你寫出靈活的,可重用的函數和定義出任何你所確定好的需求的類型。你的可以寫出避免重復的代碼,并且用一...
    iOS_Developer閱讀 807評論 0 0
  • 原文:Generics Manifesto -- Douglas Gregor 譯者注 在我慢慢地深入使用 Swi...
    kemchenj閱讀 2,058評論 0 6
  • 《月亮與六便士》 ——毛姆 “為什么討人喜歡的女人總是嫁給蠢物啊?”“因為有腦子的男人是不娶討人喜歡...
    晚晚吶閱讀 559評論 8 1