Swift 構造器的☝️思考

構造器,又叫初始化方法,想必你們都瞭解。不管是和 class 仍是 struct 打交道,都逃不了初始化這一步驟。不過最近在回看 Swift 文檔的時候,我發現了☝️以前未曾注意到的細節。html

class ClassA {
    let name: String
    init(name: String) {
        self.name = name
    }
}

class ClassB: ClassA {
    let age: Int
    init(name: String, age: Int) {
        super.init(name: name)
        self.age = age
    }
}

複製代碼

咱們在寫子類的初始化方法時,勢必須要在其中調用父類的初始化方法。而上面的初始化方法,其實會報錯,而報錯內容是:objective-c

Property 'self.age' not initialized at super.init callswift

這個錯誤提示很簡單,子類的屬性必需要在父類的初始化方法調用以前初始化,或者說白一點,self.age = age 放到 super.init(name: name) 以前就行了。可是,你們有想過,爲何要這樣嗎?這條規則背後的設計邏輯是什麼?安全

不少人首先會聯想到 Objective-C 的構造方法ide

(instancetype)init
{
    self = [super init];
    if (self != nil) {
        self.name = @"Jack";
    }
    return self;
}
複製代碼

很明顯,Objective-C 的初始化方法無一例外都首先調用了父類的構造方法,而後再初始化子類的屬性,同時這也是符合開發人員直覺的。那爲啥 Swift 恰恰不行呢?難道是 Swift 的特有機制致使的?flex

讓咱們看看 Swift 官方文檔 是怎麼說的。Swift 中的類初始化是一個分爲兩個階段的過程。 這其中,編譯器會執行四個有用的安全檢查,以確保構造器完成時沒有錯誤。這其中的第一條規則就是:ui

A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.spa

指定的初始化程序必須確保其類引入的全部屬性在委託給超類初始化程序以前都已初始化 。設計

而對於第一條規則的解釋,雖然沒有明確寫在規則以後,但 Apple對於 兩個階段 四個安全檢查 的最終目的是很明確的:code

The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they are initialized, and prevents property values from being set to a different value by another initializer unexpectedly.

兩階段初始化過程的使用使初始化安全,同時仍爲類層次結構中的每一個類提供了徹底的靈活性。兩階段初始化可防止在初始化屬性值以前對其進行訪問,並防止其餘初始化程序意外地將屬性值設置爲其餘值。

這個解釋彷佛有些抽象,後來看到了一個博客舉的一個例子,才恍然大悟。

class ClassA {
    let name: String
    init(name: String) {
        self.name = name
        description()
    }

    func description() {
        print("我已經初始化好啦,個人名字是: \(name)")
    }
}

class ClassB: ClassA {
    let age: Int
    init(name: String, age: Int) {
        self.age = age
        super.init(name: name)
    }

    override func description() {
        print("我已經初始化好啦,個人名字是: \(name), 個人年齡是: \(age)")
    }
}
複製代碼

思考一下,子類屬性的初始化和父類的初始化方法的調用順序,真的沒有任何影響嗎?假如咱們先初始化父類的構造方法,而後再去給 age 賦值,結果會怎麼樣?

很明顯,最終程序將沒法運行,由於此時父類調用子類的 description 方法中, age 屬性還還沒有初始化。注意,因爲 age 使用了 let 關鍵詞修飾,所以其屬性必需要在構造器中初始化。在構造器初始化 age 屬性以前, age 的值都沒法訪問。沒法訪問的意思不是指 age 的值爲 nil, 而是沒有被初始化,即內存還沒有被分配。這一點和 Objective-C 不一樣,由於在 Objective-C 中,屬性聲明後便有了初始值 nil ,所以即使在構造器中未對其初始化,也能夠訪問屬性的值(nil)。對於這一點,Swift 文檔也在其中進行了說明。

Swift’s two-phase initialization process is similar to initialization in Objective-C. The main difference is that during phase 1, Objective-C assigns zero or null values (such as 0 or nil) to every property. Swift’s initialization flow is more flexible in that it lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value.

Swift 的兩階段初始化過程相似於 Objective-C 中的初始化。主要區別在於,在階段 1 中,Objective-C 爲每一個屬性分配零或空值(例如 0 或 nil )。Swift 的初始化流程更加靈活,由於它可讓您設置自定義初始值,而且能夠處理有效值 0 或 nil 無效值的類型。

正由於 Swift 的構造器更加靈活(容許不可變屬性在構造器中初始化),也更加安全(屬性須要在初始化後才能訪問),所以也不難理解 Apple 對 Swift 的構造器有額外的要求。

相關文章
相關標籤/搜索