我們今天繼續我們的初始化器
Class 裡面的所有存儲屬性(包括從父類繼承的人和屬性)都必須初始化的期間中設置初始值。
Swift 裡面為 Class 提供了兩種初始化器,來確保實例中所有存儲屬性都能獲得初始值,分別是指定初始化器跟便利初始化器。
指定初始化器是最主要的初始化器。指定初始化器會初始化 Class 所提供的所有屬性,並調用合適的父類初始化器讓初始化器沿著父類鏈網上進行。
每一個 Class 都至少擁有一個指定初始化器,只有在某些情況下 Class 繼承了父類裡面的指定初始化器。
便利初始化器是 Class 裡面比較次要的初始化器,屬於輔助型的初始化器。可以定義便利初始化器來調用同一個 Class 裡面的 指定初始化器,並為部分提供初始默認值。或是用便利初始化器創建一個特殊用途或特定輸入值得實例。
指定初始化器
init(parameters) {
statements
}
便利初始化器
convenience init(parameters) {
statements
}
為了簡化指定初始化器和便利初始化器之間的調用關係,Swift 初始化器之間的委託調用遵循以下三點:
也可以用簡單的方式記住這些
官方有圖片來描述這些規則
如圖片所示,父類中包含一個指定初始化器和兩個便利初始化器。其中一個便利初始化器調用了另外一個便利初始化器,而後者又調用了唯一的指定初始化器。而這滿足了 2 和 3 的規則。而父類本身沒有其他父類所以不用符合規則 1 。
子類中包含兩個指定初始化器和一個便利初始化器。便利初始化器必須調用兩個指定初始化器中的任意一個,因為他只能調用同一個 Class 裡面的其他初始化器。這滿足了 2 和 3 規則。而兩個指定初始化器必須調用父類中唯一的指定初始化器,這滿足規則 1 。
下圖有更複雜的 Class 層級結構。他演示了指定初始化器是如何在 Class 層級中充當管道使用,在 Class 的初始化鏈上簡化了 Class 之間的相互關係。
Swift 中 Class 的初始化過程有兩個階段,階段一,Class 中的的每個存儲屬性賦予一個初始值。當存儲屬性都被賦值後,才會進入第二階段,給 Class 可以在新實例準備使用前自定義他們的存儲屬性。
兩段式初始化過程的使用讓初始化過程更安全,同時在整個 Class 層級結構中給予了每個 Class 完全的靈活性。兩段式初始化過程可以防止屬性值在初始化之前就被訪問,有可以防止屬性被另一個初始化器意外的賦予不同的值。
Swift 會進行 4 種有效的安全檢查,以確保兩段式初始化過程不出錯
如上所述,一個對象的內存只有其所有存儲屬性確定之後才能完全初始化。為了滿足這一條規則,指定初始化器必須保證他在 Class 的屬性他往上代理時就完成初始化。
Class 的實力在第一階段結束以前並不是完全有效的。只有第一階段結束後, Class 的實力才是有效的,才能訪問屬性和調用方法。
以下是基於上述安全檢查的兩段式初始化過程展示:
階段一:
階段二:
下圖展示了在假定的子類和父類之間的初始化階段一:
在途中,初始化過程對子類中一個便利初始化器的調用開始。這個便利初始化器此時不能修改任何屬性,他會代理到該類中的指定初始化器。
如安全檢查1. 所示,指定初始化器將確保所有子類的屬性都有值。然後他將調用父類的指定初始化器。並沿著繼承鏈一直往上完成父類的初始化過程。
父類中的指定初始化器確保了所有父類的束性都有值。由定喔服類要初始化,有就無需繼續向上代理。
一但父類中都有了初始值,實力的內存才是被認為有完全初始化,第一階段完成。
以下展示了初始化過程的階段二:
父類中指定初始化器現在有機會進一步自定義實例
一但父類中的指定初始化器完成調用,子類中的指定初始化器可以執行更多自定義操作
最終,一但子類的指定初始化器完成調用,最開始被調用的便利初始化器可以值性更多的自定義操作。
假如你希望自定義中的子類能提供一個或多個跟父類相同的初始化器,你可以在子類中提供這些初始化器自定義實現。
當你在編寫一個和父類中指定初始化器相匹配的子類初始化器時,你實際上就是在重寫父類的這個指定初始化器。因此,你必須在定義子類初始化器時加上 override 。即時你重寫的是翕動自動提供的默認初始化器,也需要加上 override 。
正如重寫屬性一樣,方法或者是下標 override 會讓編譯器去檢查父類中是否有匹配的指定初始化器,並驗證初始化器參數是否有被指定。
相反的如果你編寫一個和父累便利初始化匹配的子類初始化器,由於子類不能直接調用父類的便利初始化器,因此,你的子類並未對一個副類初始化器提供重寫,結果是你在子類中 重寫 一個父類便利初始化器時不需要加 override 。
接下來我們來看官方的例子
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle 這個 Class 只有給存儲行屬性默認值,沒有特別定義自己的初始化器。因此他就是有一個系統給的默認初始化器。
let vehicle = Vehicle()
print("Vehicle: \(vehicle,description)")
// Vehicle: 0 wheel(s)
上面實例化了 Vehicle
下面的例子讓 Bicycle 繼承了 Vehicle
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
依照前面說的,如果子類要自定義自己的初始化器,就要對父類的初始化器做重寫,所以要加上 override 在前面進行修改。super.init() 就是要先知道父類裡面的 init 是長怎樣。
所以實例化之後會像下面這樣
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印“Bicycle: 2 wheel(s)”
如上所述,子類在默認狀況下不會繼承父類的初始化器。但是如果滿足特定條件,父類初始化器是可以自動被繼承的。這樣你就不用重寫父類的初始化器並可以在安全的狀況下以最小的代價繼承父類的初始化器。
假設你為子類中引入的新屬性都提供了默認的值。以下兩個規則都適用:
即時你在子類添加了更多的便利初始化器,這兩條規則都仍然適用。
下面來看官方的操作過程例子
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
這個 Food 的 Class 裡面兩個初始化器和一個 name 的 String 類型的存儲類型。
下圖中展示了 Food 初始化器鏈
Class 沒有默認的逐一成員初始化器,所以 Food 提供了一個接受單一參數的 name 的指定初始化器。
let nameMeat = Food(name: "Bacon")
// nameMeat 的名字是 Bacon
Food 初始化器是一個指定初始化器,因為他裡面的存儲屬性都有被初始化。然後 Food 沒有父類所有不需要調用 super.init() 。
接下來我們看這個官方例子
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
下圖展示了這個 Class 的初始化器鏈
RecipeIngredinet 這個類擁有一個指定初始化器,可以來實例他裡面的所有屬性值。這個初始化器一開始先將傳入的 quantitiy 參數費與給 quantitiy 屬性,這個屬性也是唯一在 RecipeIngredinet 中新引入的屬性。初始化器向上代理到父類 Food 的 init(name: String)。
RecipeIngredinet 也定義了一個便利初始化器,他通過 name 來創建實例。這個便利初始化器假設任意的 quantity 為 1 ,所以不需要顯示的質量也可以創建出實例。為了不要重複創建多個 quantity 為 1 的實例,這個便利初始化器只是簡單的橫向代理到 Class 中的指定初始化器。
而 RecipeIngredinet 的便利初始化器使用了 Food 的指定初始化器,所以前面要加上 override 。
僅管 RecipeIngredinet 將父類的指定初始化器重寫,為了便利初始化器,但是他依舊提供了父類所有的指定初始化器的實現。所以他會自動繼承父類的所有便利初始化器。
好的,今天就先到這裡了