Swift中類和子類的初始化有一定的規(guī)則,不是很容易理解,現(xiàn)在簡單梳理一下:
兩段式初始化( two-phase initialization)
首先要記住一點,Swift中類的初始化的時候,所有存儲屬性(Stored Property)要么有初始值,要么在初始化的時候通過init賦值(optional 除外),否則編譯不通過,比如:
class Car {
let wheel: Int
let windows: Int
init(windows: Int) {
self.windows = windows
} // Return from initializer without initializing all stored properties
}
只有所有存儲屬性都初始化了,才可以編譯通過:
class Car {
let wheel: Int = 4
let windows: Int
init(windows: Int) {
self.windows = windows
}
} // 編譯通過
這個規(guī)則同樣適用于子類,如果子類繼承父類后,引入了新的存儲屬性,那么新的存儲屬性也有相同的規(guī)則,比如下面的子類因為沒有初始化新的存儲屬性而編譯不過:
class FordCar:Car {
let year: Int
init(windows: Int) {
super.init(windows: windows) // Property 'self.year' not initialized at super.init call
}
}
而且我們發(fā)現(xiàn),子類的存儲屬性初始化必須在調(diào)用父類初始化之前,比如下面還是編譯不過:
class FordCar: Car {
let year: Int
init(windows: Int, year: Int) {
super.init(windows: windows) // Property 'self.year' not initialized at super.init call
self.year = year // Immutable value 'self.year' may only be initialized once
}
}
那么子類的初始化遵循什么規(guī)則呢?Swift官方的規(guī)定是兩段式初始化( two-phase initialization):
- 第一階段:自底向上(從子類到父類),初始化所有的儲存屬性
- 第二階段:第一階段結(jié)束后,才能使用屬性和方法
簡單的說,第一階段規(guī)定了子類新屬性的初始化要放到super.init之前,比如:
class Car {
let wheel: Int = 4
let windows: Int
init(windows: Int) {
self.windows = windows
}
}
class FordCar: Car {
let year: Int
init(windows: Int, year: Int) {
self.year = year
super.init(windows: windows)
}
}
class SuperFordCar: FordCar {
var power: Double
init(windows: Int, year: Int, power: Double) {
self.power = power
super.init(windows: windows, year: year)
}
}
對于第二階段,簡單的理解就是使用屬性或者方法要在super.init之后,否則使用屬性或者方法的時候,可能父類或者子類的屬性都還沒有初始化,接上面的例子,比如下面的用法編譯不過:
class SuperFordCar: FordCar {
var power: Double
init(windows: Int, year: Int, power: Double) {
self.power = power
allPower() // 'self' used in method call 'allPower' before 'super.init' call
super.init(windows: windows, year: year)
}
func allPower() {
power = power * Double(wheel)
print(power)
}
}
allPower()方法必須要在父類初始化之后才能使用
理解了兩段式初始化之后,下面我們再理解一下必須初始化(Required initializers)和 便利初始化(convenience initializers)
必須初始化 (Required initializers)
假設(shè)父類有多個初始化方法:
class FordCar: Car {
let year: Int
init(windows: Int, year: Int) {
self.year = year
super.init(windows: windows)
}
init(standWindows: Int = 4, currentYear: Int = 2020) {
year = currentYear
super.init(windows: standWindows)
}
}
這時,子類繼承的時候可以選擇任意一個父類的初始化方法作為自己初始化時候需要調(diào)用的方法,但這里選擇的空間過大,Swift因此引入了required關(guān)鍵字,用來修飾父類的初始化方法,使得子類初始化的時候必須重載(override)這個必須初始化 (override關(guān)鍵字可以省去):
class Car {
let wheel: Int = 4
let windows: Int
init(windows: Int) {
self.windows = windows
}
required init(standardWindows: Int = 4) {
self.windows = standardWindows
}
}
class FordCar: Car {
let year: Int
init(windows: Int, year: Int) {
self.year = year
super.init(windows: windows)
}
required init(standardWindows: Int = 4) {
self.year = 4
super.init(standardWindows: standardWindows)
}
}
let fordCar = FordCar(standardWindows: 3)
fordCar.windows
這里需要注意的是,子類重載必須初始化的時候,還是需要遵從兩段式初始化規(guī)定,即在super.init前初始化新的屬性,在super.init后才能使用屬性和方法。
如果protocol有指定初始化方法,那里實現(xiàn)這個protocol的類要使用必須初始化實現(xiàn)這個protocol指定的初始化方法,比如下面的代碼編譯不過:
protocol CarType {
init(standardWindows: Int)
}
class Car: CarType {
let wheel: Int = 4
let windows: Int
init(windows: Int) {
self.windows = windows
}
init(standardWindows: Int = 4) {
self.windows = standardWindows
}
} // Initializer requirement 'init(standardWindows:)' can only be satisfied by a 'required' initializer in non-final class 'Car'
在第二個init前加入關(guān)鍵字required就通過了。
這里也解釋了為什么繼承很多UIKit的類(比如UITableViewCell),需要添加required init?(coder aDecoder: NSCoder)的原因,因為這些UIKit的類實現(xiàn)了NSCoding protocol,比如UITableViewCell就實現(xiàn)了NSCoding protocol,NSCoding protocol中有指定初始化方法:
public protocol NSCoding {
func encode(with coder: NSCoder)
init?(coder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
便利初始化(convenience initializers)
可以在init前加入convenience關(guān)鍵字便可實現(xiàn)便利初始化,便利初始化可以調(diào)用其他的便利初始化,但最終中必須調(diào)用一個非便利的初始化,比如:
class Car {
let wheel: Int = 4
let windows: Int
init(windows: Int) {
self.windows = windows
}
convenience init(standardWindows: Int = 4) {
self.init(windows: standardWindows)
}
}
便利初始化不能被子類調(diào)用,不用遵循兩段式初始化的規(guī)則。
指定初始化(designated initializer)
指定初始化(designated initializer)其實就是非便利初始化,包括常規(guī)的初始化init和必須初始化required initializer,遵循兩段式初始化的規(guī)則。
總結(jié)
說了這么多,其實初始化的方法可以總結(jié)如下:
- 類初始化必須完成的一個任務(wù)就是讓所有的存儲屬性都有初始值(optional 除外)。
- 如果父類有指定初始化,子類必須也有指定初始化,并且必須調(diào)用父類的其中一個指定初始化(如果是必須初始化,就是重載),并遵循兩段式初始化的規(guī)則。
- 一個便利初始化必須調(diào)用同一類中的初始化方法(可以是另一個便利初始化,也可以是指定初始化),但最終一定會調(diào)用到一個指定初始化。
- 便利初始化不遵循兩段式初始化的規(guī)則,不能被子類調(diào)用或者重載。