【Swift 3.1】23 - 泛型 (Generics)

【Swift 3.1】23 - 泛型 (Generics)

自從蘋果2014年發(fā)布Swift眯亦,到現(xiàn)在已經(jīng)兩年多了侮叮,而Swift也來到了3.1版本姿搜。去年利用工作之余笤昨,共花了兩個多月的時間把官方的Swift編程指南看完×媒溃現(xiàn)在整理一下筆記停士,回顧一下以前的知識。有需要的同學可以去看官方文檔>>完丽。


泛型代碼能讓我們編寫靈活恋技、可重復使用的方法和類型÷咦澹可以避免編寫重復代碼蜻底,并以清晰和抽象的方法表達其目的。

泛型是Swift非常強大的一個特性聘鳞,并且很多Swift標準庫都是用泛型編寫的薄辅。實際上我們已經(jīng)使用過泛型,例如Swift的ArrayDictionary都是泛型集合搁痛。

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

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

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

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

使用泛型代碼讓我們用一個方法就能解決上面的問題源请。

泛型方法 (Generic Functions)

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

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

這個通用的方法使用T作為類型名稱,而不是Int彻况、String或者Double谁尸。T沒有說明必須是什么類型,但是ab的類型必須是一樣的纽甘。

方法名后面還有一個<T>良蛮,告訴Swift只是一個占位類型,不是實際類型悍赢。

swapTwoValues(_:_:)方法可以像之前的swapTwoInts一樣調用决瞳,只要兩個用于交換的值類型是一樣的货徙。

用通用的方法交換兩個String類型的值或者兩個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"

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

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

上面的泛型方法swapTwoValues(_:_:)屡贺,占位類型T其實是一個類型參數(shù)蠢棱。類型參數(shù)指定并命名占位類型,緊跟在方法名后面甩栈,用<T>表示泻仙。

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

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

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

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

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

泛型類型 (Generic Types)

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

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

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

下圖演示了棧的push/pop:

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

下面是一個不通用的IntStack:

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

下面是一個通用版本的Stack:

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

Element定義了一個占位名字巷蚪,并且在三個地方被用到:

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

因為它是一個通用類型,所以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

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

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

下面是對Stack的擴展:

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

泛型類型的擴展也可以指定一些限定條件前联,滿足這些條件才可以訪問擴展里面的成員,下面會講到娶眷。

類型約束 (Type Constraints)

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

例如,Swift的Dictionary限定了鍵的類型豌注。字典的鍵必須是hashable的伤塌,也就是說,必須提供一個方法保證它自己是唯一的轧铁,然后字典才能根據(jù)這個鍵去查找對應的值每聪。Swift的基本類型默認都是hashable的,例如String齿风、Int药薯、DoubleBool

類型約束語法 (Type Constraint Syntax)

語法如下:

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

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

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

下面是一個不通用的findIndex(ofString:in:),用于查找指定字符串在字符串數(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
}

其實這個方法不能編譯通過脸候,因為value == valueToFind穷娱。不是所有Swift類型的都可使用==進行比較。

Swift定義了Equatable協(xié)議纪他,要求所有遵循這個協(xié)議的類型必須實現(xiàn)==!=運算符鄙煤。Swift的標準類型都遵循了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

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

當定義協(xié)議時梯刚,定義一個或多個關聯(lián)類型有時候是非常有用的。關聯(lián)類型提供了一個類型名字薪寓,然后這個類型可以在協(xié)議中使用亡资。使用associatedtype關鍵字來定義關聯(lián)類型澜共。

關聯(lián)類型實踐 (Associated Types in Action)

下面是一個例子:

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

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

之前那個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是一個Int類型嗦董。typealias Item = Int把抽象的Item轉變?yōu)榫唧w的Int

因為Swift有類型推斷功能瘦黑,所以我們可以不用寫typealias Item = Int京革,因為IntStack實現(xiàn)了Container協(xié)議的所有要求,Swift能推斷出需要用的Item的具體類型幸斥。

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

我們可以使用擴展來遵循某一個協(xié)議匹摇,當然這個協(xié)議也可以是由關聯(lián)類型的協(xié)議。

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

extension Array: Container {}

我們就可以把Array當做是一個Container经窖。

泛型的Where語句 (Generic Where Clauses)

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

例如下面這個例子:

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(_:_:)方法的實際使用:

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語句的擴展 (Extensions with a Generic Where Clause)

例如下面這個例子:

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

如果沒有泛型where語句配乱,將會出現(xiàn)一個問題:isTop(_:)方法使用了==運算符屿脐,但是Stack沒有要求它的元素是可以比較的,所以使用==將會編譯錯誤宪卿。

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

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

下面是使用where擴展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."

上面對Container的擴展要求Item遵循Equatable協(xié)議的诵,我們還可以要求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"

我們還可以在where語句中可以添加多個約束條件,條件之間用逗號隔開即可佑钾。


第二十三部分完西疤。下個部分:【Swift 3.1】24 -訪問權限


如果有錯誤的地方,歡迎指正休溶!謝謝代赁!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兽掰,隨后出現(xiàn)的幾起案子芭碍,更是在濱河造成了極大的恐慌,老刑警劉巖孽尽,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窖壕,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機瞻讽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門鸳吸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人速勇,你說我怎么就攤上這事晌砾。” “怎么了烦磁?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵养匈,是天一觀的道長。 經(jīng)常有香客問我都伪,道長乖寒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任院溺,我火速辦了婚禮,結果婚禮上磅轻,老公的妹妹穿的比我還像新娘珍逸。我一直安慰自己,他們只是感情好聋溜,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布谆膳。 她就那樣靜靜地躺著,像睡著了一般撮躁。 火紅的嫁衣襯著肌膚如雪漱病。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天把曼,我揣著相機與錄音杨帽,去河邊找鬼。 笑死嗤军,一個胖子當著我的面吹牛注盈,可吹牛的內容都是我干的。 我是一名探鬼主播叙赚,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼老客,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了震叮?” 一聲冷哼從身側響起胧砰,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苇瓣,沒想到半個月后尉间,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年乌妒,在試婚紗的時候發(fā)現(xiàn)自己被綠了汹想。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撤蚊,死狀恐怖古掏,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情侦啸,我是刑警寧澤槽唾,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站光涂,受9級特大地震影響庞萍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜忘闻,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一钝计、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧齐佳,春花似錦私恬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硅蹦,卻和暖如春荣德,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背童芹。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工涮瞻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人假褪。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓饲宛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嗜价。 傳聞我的和親對象是個殘疾皇子艇抠,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容

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