iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0
Software Development

Swift 菜鳥的30天系列 第 19

Day-19 Swift 語法(15) - 初始化 Initialization

廢話區

初始化的內容真的好多好複雜,明天繼續初始化新的一天,一天寫不完R

初始化(Initialization)

初始化是準備要使用的 Class , Struct 或 Enum 的實例的過程,該過程為該實例上的每個存儲屬性設置一個初始值,並在新實例準備好使用之前執行所需的任何其他設置或初始化。透過定義初始化來實現這個初始化過程,就像特殊的方法,可以被使用它來創建一個特定類型的新實例。


為存儲屬性設置初始化值

在創建 Class 和 Struct 的實例時必須為所有的存儲屬性設置一個合適的初始值。存儲屬性不能遺留在不確定的狀態中。你可以在初始化器中為存儲屬性設置一個初始值,或者通過分配一個默認的屬性值作為屬性定義的一部分。


初始化器

調用初始化器來創建特定類型的新實例。以最簡單的形式,初始化器就像一個沒有參數的實例方法,使用init關鍵字來編寫:

init() {
    // 執行初始化
}

我們下面用一個 Long 的 struct 來儲存一個寬度, 裡面有一個名為 width 變數類型為 Int :

struct Long {
    var width:Int
    init() {
        width = 100
    }
}

這個 struct 定義了一個沒有參數的初始化器,他的初始化儲存的寬度為 100,我們一樣放到實例中測試:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701qDeRc2B5kd.png

默認的初始化

你可以像上面的範例在初始化過程中儲存屬性的初始值,或者你也可以指定默認屬性值作為宣告的一部分,透過定義屬性時來指定默認的屬性值。

所以上面的例子我們也能用很簡單的方式編寫:

struct Long {
    var width = 100
}

自定義初始化

初始化參數

你可以提供初始化參數作為初始化器的一部分,來定義初始化過程中的類型和值的名稱。初始化參數與函數和方法的參數具有相同的功能和語法。

下面我們定義一個長度換算的範例,我們創建了兩個初始化器 init(cm centimeter:Double) 和 init(km kilometer:Double) , 這兩個初始化器都把它們的參數轉換為了公尺並且把這個值儲存到了名為 meter 的屬性裡。

struct Long {
    var meter:Double
    init(cm centimeter:Double) {
        meter = centimeter / 100
    }
    init(km kilometer:Double) {
        meter = kilometer * 1000
    }
}

放到實例中測試結果:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701T6sdElXlIG.png


參數名稱及參數標籤

與函數和方法的參數一樣,初始化參數也可以在初始化器內部有一個變數名以及參數標籤供調用的時候使用。初始化器並不能像函數和方法那樣在圓括號前面有一個函數名稱。因此,一個初始化器的參數名稱和類型在識別該使用哪個初始化器的時候就顯得非常重要。所以,如果你沒有提供外部名稱 Swift 會自動為每一個形式參數提供一個外部名稱。

下面我們創建一個 Color 的 Struct, 裡面有三個儲存了色彩三原色 red , blue , green 的常數,以及我們為三原色提供了一個初始化器,帶有三個 Double 類型參數。另一個提供了只有一個 black 參數的初始化器,它用來給三原色設置相同的值:

struct Color {
    let red, blue, green:Double
    init(red:Double,blue:Double,green:Double) {
        self.red = red
        self.blue = blue
        self.green = green
    }
    init(black:Double) {
        self.red = black
        self.blue = black
        self.green = black
    }
}

之後你可以調用它的不同初始化方式創建實例,可以創建某個顏色的實例或是黑色的實例:

https://ithelp.ithome.com.tw/upload/images/20180107/20107701nQM58jYnGP.png

如果你沒有使用參數標籤調用它,如同下面這個例子則會回報錯誤:

let someColor = Color.init( 0.5, 0.5,0.5)
//  缺少參數標籤 'red:blue:green:' 的調用

無實際參數標籤的初始化器參數

如果你不想為初始化器參數使用參數標籤,可以寫一個下劃線( _ )替代明確的實際參數標籤以重寫默認行為。

我們用前面的 Long 長度的範例加上一個沒有實際參數標籤的例子:

struct Long {
    var meter:Double
    init(cm centimeter:Double) {
        meter = centimeter / 100
    }
    init(km kilometer:Double) {
        meter = kilometer * 1000
    }
    // 新建的無參數標籤的初始化參數
    init(m meter:Double){
        self.meter = meter
    }
}

之後調用這個初始化器只需他輸入的值即可:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701owJp9le2p2.png


可選屬性類型

如果你自定義的類型是邏輯上是允許他 “無值” 的儲存屬性,也許是他的值不能在初始化過程中設置,或者因為它在稍後允許設置為 “無值”的屬性——宣告屬性是可選類型。可選類型的屬性會自動初始化為 nil ,表示屬性該屬性在初始化期間故意設置為 “沒有值”。

下面我們舉一個問答的範例,其中有一個 response 被我們設置為可選的 String 類型 :

class AskBot {
    var text:String
    var response:String?
    init(Qus text: String) {
        self.text = text
    }
    func ask(){
        print(text)
    }
}

在初始化時,我們的 response 會被初始化自動設為 nil 我們可以在實例中賦值給他:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701FlXwMhHPvT.png
https://ithelp.ithome.com.tw/upload/images/20180107/20107701HYBED2tovS.png


在初始化中分配常量屬性

可以為常數設置一個值在初始化的任何時刻,只要在初始化完成時間將它設置一個確定的值即可。一但常數被賦予一個值之後就不能修改。所以我們上頭中的範例中的 text ,我們可以將其用 let 宣告為一個常數。

class AskBot {
    let text:String
    var response:String?
    init(Qus text: String) {
        self.text = text
    }
    func ask(){
        print(text)
    }
}

如果你一但宣告為常數之後就不能像上述範例在實例中更改 text 的值。


默認初始化器

Swift 為沒有提供初始化器的 struct 或 class 提供了一個默認的初始化器來給所有的屬性提供了默認值。這個默認的初始化器只是創建了一個所有屬性都有默認值的新實例。

我們新增一個叫做 Work 的 Class,並在其封裝了公司名稱、人數及是否公開的屬性:

class Work {
    var company = "好想工作室"
    var people = 30
    var isPublic = true
}

由於這個 Class 所有屬性都有默認值,又由於它是一個沒有父類的基類,所以它自動地獲得了一個默認的初始化器,使用默認值設置了它的所有屬性然後創建了新的實例。

我們使用默認初始化器以及初始化器語法創建新的實例,並且給這個新實例賦了一個名為 work 的變數。
https://ithelp.ithome.com.tw/upload/images/20180107/20107701rwxWhtdynR.png


結構體類型的成員初始化器

如果 Struct 沒有定義任何他的初始化器,那麼 Struct 會自動接收他的成員初始化器。跟默認初始化器不同的是,即使 Struct 儲存了沒有默認值的屬性,Struct 也會接收了成員初始化器。這個成員初始化器是快速初始化新結構體實例成員屬性的方式,新實例的屬性初始值可以通過其名稱傳遞給成員初始化器。

我們創建一個立方體的結構體,其成員有長寬高:

struct Cube {
    var length = 0 ,  width = 0,height = 0
}

我用它來初始化一個 Cube 的新實例,並透過屬性名稱傳遞初始值:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701PzZ8UYzQDM.png


值類型的初始化器委託

初始化器可以調用其他的初始化器來執行實例初始化的一部分,這個過程我們就稱之為初始化器委託,它避免了在多個初始化器中重複的程式碼。

初始化委託的運作以及那些允許形式的委託的規則,對於值類型 ( value type ) 和類類型 (class type ) 是不同的。值類型( struct 和 enum ) 不支援繼承,所以他們的初始化器委託的過程相對簡單,因為他們只能委託給另一個他們自己提供的初始化器。然而 Class 可以繼承其他 Class,如同繼承所述的那樣。這意外這 Class 有著額外的責任,以確保他們繼承的所有儲存的屬性在初始化過程中被分配一個合適的值。

對於值類型,當你寫自己自定義的初始化器時可以使用 self.init 從相同的值類型裡引用其他初始化器。你只能從初始化器裡調用 self.init 。

下面我們創建一個立方體和三原色的 struct 來進行初始化器委託的應用:

struct Cube {
    var length = 0 , width = 0,height = 0
}

struct Color {
    var red = 0.0 , green = 0.0, blue = 0.0
}

你可以用下面的三個方式中的任意一個來初始化下面的 CubeColor 這個 struct ,透過使用默認初始化 cube 和 color 屬性值:

struct CubeColor {
    var cube = Cube()
    var color = Color()
    // 1
    init(){}
    // 2
    init(cube: Cube, color: Color) {
        self.cube = cube
        self.color = color
    }
    // 3
    init(defaultCube: Cube, defaultColor: Color) {
        self.cube = Cube(length: 130, width: 130, height: 130)
        self.color = Color(red: 245, green: 225, blue: 215)
    }
}

測試結果如下:
https://ithelp.ithome.com.tw/upload/images/20180107/20107701CcoA9ZeV3M.png


上一篇
Day-18 Swift 語法(14) 繼承 Inheritance
下一篇
Day-20 Swift 語法(16) - Class 與 初始化器
系列文
Swift 菜鳥的30天30

尚未有邦友留言

立即登入留言