相关文章推荐

第三章 - 泛型(Generic) 是什麼? 有什麼好處?

泛型代碼可以讓你寫出根據自我需求定義、適用於任何類型的,靈活且可重用的函數和類型。它的可以讓你避免重複的代碼,用一種清晰和抽像的方式來表達代碼的意圖。

你可能在C++中,聽到的術語是 Template , 而Swift,Java和C#則採用 Generic (泛型),但概念是相近的。

在Swift中,泛型可作用於幾個地方:

  • Function
  • Classess/Structs/Enums
  • Protocol
  • Extension
  • 1. Function

    在以往,我們可能會有幾個function,做很像的事情: 例如,交換兩個數字,交換兩個字串

    //交換兩個數字
    func swapInts(inout lhs: Int, inout _ rhs: Int){
        //先把lhs存到temp
        let temp = lhs
        //把rhs賦值到lhs
        lhs = rhs
        //最後把temp賦值到rhs
        rhs = temp
    var a = 10
    var b = 11
    swapInts(&a, &b)
    a //11
    b //10
    //交換兩個字串
    func swapStrings(inout lhs: String, inout _ rhs: String){
        //先把lhs存到temp
        let temp = lhs
        //把rhs賦值到lhs
        lhs = rhs
        //最後把temp賦值到rhs
        rhs = temp
    var aString = "Hello"
    var bString = "World"
    swap(&aString, &bString)
    aString //World
    bString //Hello
    

    swapInts做的事,和swapStrings是不是很像呢?😏

    所以這個時候,泛型就派上用場了 我們可以定義一個function叫swapValues,並透過泛型的Type來通吃對換這件事

    a. 泛型Function的語法一:

    func FName<Type>(a: Type, [Arguments...])->Void

    代表泛型對象代表的Type 將透過傳入值的Type來動態決定

    //一個通用於任何型別的swapValues的函式
    func swapValues<Type>(inout lhs: Type, inout _ rhs: Type){
        //先把lhs存到temp
        let temp = lhs
        //把rhs賦值到lhs
        lhs = rhs
        //最後把temp賦值到rhs
        rhs = temp
    //所以我們可以這樣使用就可以了:
    var c = 12
    var d = 13
    swapValues(&c, &d)
    c //13
    d //12
    var cString = "123"
    var dString = "456"
    swapValues(&cString, &dString)
    cString //456
    dString //123
    

    b.泛型Function語法二:

    func FName<Type>([Arguments...])->Type

    代表Type的依據是透過傳出值決定

    func convertTo<Type>(value: Any)->Type?{
        //這裡只先做單純的轉型
        //如果型別可以強轉成功,就回傳,沒有就會回傳一個Optional
        return value as? Type
    let str = "123"
    let outputStr:String? = convertTo(str)
    let outputInt:Int? = convertTo(str) //這裡就會回傳出nil
    

    c.泛型Function語法三:

    func FName<Type>(a: Type, [Arguments...])->Type

    代表Type的依據是透過傳入值和回傳值兩個相同的Type決定

    func test<Type>(a: Type)->Type?{
        return a
    let output:String? = test("124")
    let output:Int? = test("123") //這一句會失敗,因為傳入的是字串,但輸出是 Int? 就不成立
    
    Swift內建中的Function, swap就是泛型的Function,有機會可以自已查詢看看定義喔。

    2. Class/Struct/Enum

    Swift中,Generic也可以使用在Class/Struct/Enum,可以所定義之型態依據不同的需求產生不同的使用。常見的如Array/Dictionary就是泛型的容器用法。

    語法糖衣說明:

  • [Int] ==> Array
  • [String:Any] ==> Dictionary
  • let intArray:[Int] = Array<Int>(count: 10, repeatedValue: 10)
    let dict:[String:Any] = Dictionary<String, Any>(dictionaryLiteral: ("name", "grady"))
    

    來看看泛型實際上可以幫到你什麼?

    假設我們有一個Person的Struct,先不要搞得太複雜,只有

  • 身份證字號
  • 這個例子是描寫一個可能的情境

  • 身份證字號有可能是數字,也可能是字串
  • 名字可以是一個單純的字串,但也可以是一個複雜的Struct/Class/Enum
  • Ok, 要開始囉~

    struct Person<T, U> : CustomStringConvertible {
        // 身份證字號,型別由外部傳入的第一個順位型別:T 決定
        var identifier:T
        // 姓名,型別由外部傳入的第二個順位型別:U 決定
        var name:U
        //實作CustomStringConvertible
        var description:String{
            return "\(self.identifier) : \(self.name)"
    //如果身分證字號和姓名都是字串
    let personByString = Person<String, String>(identifier: "124146828", name: "Grady Zhuo")
    //如果身分證字號是整數和姓名是Name的struct
    let personByIntName = Person<Int, Name>(identifier: 124146828, name: Name(first: "Max", last: "Chen"))
    

    覺得寫成Person< String, Name >…很麻煩嗎?

    1. 如果有建構式可以決定泛型型態,那就可以不用寫這樣的東西
    let personByIntName2 = Person(identifier: 124146828, name: Name(first: "John", last: "Yeh", middle: "Boss"))
    
    2. 另外你也可以透過 typealias 來幫你自定型別的名字
    typealias StringPerson = Person<String, String>
    //接下來就可以用StringPerson來建構person,而T, U就不用再進行定義囉
    let person = StringPerson(identifier: "L123345678", name: "Hello world")
    

    3. Protocol

    Protocl的泛型,嚴格來說不算是標準的Generic寫法,但透過要求protocol實作者完成型別的定義,以達成模擬Generic在多型別定義的效果。

    protocol Animal {
        //可以透過 typealias Name 要求實作者完成Name的完整定義
        typealias Type
        //後續就可以使用Type去定義自已需要的參數
        var type:Type { get }
    struct Cat : Animal {
        typealias Type = AnimalType
        var type:Type {
            var type = AnimalType()
            type.科 = .貓科
            return type
    struct Dog : Animal {
        typealias Type = String
        var type:Type {
            return "狗狗"
    let cat = Cat()
    cat.type
    let dog = Dog()
    dog.type
    

    4. Extension

    Extension 泛型的類型的時候,可以直接使用其泛型定義的類型

    extension Person {
        //傳入的name型別是U, 因為前面Person就已經宣告好了。
        mutating func changeName(name: U){
            self.name = name
    //先產生一個grady
    var grady = Person<String, String>(identifier: "123", name: "Grady")
    //因為grady設定的是String,所以可以接受字串的Hello
    grady.changeName("Hello")
    //但下面這一句就不行了
    let name = Name(first: "Hello", last: "World", middle: nil)
    grady.changeName(name) //無法接受Name型別的物件
    
     
    推荐文章