Swift類和子類的初始化總結(jié)

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)用或者重載。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容