Swift 中的 Designated Initializer 和 Convenience Initializer

在寫程式中,初始化 (Initializer) 是我們最不可避免的函式,我們往往會寫了一堆函式來初始化物件,但為了重複利用,所以我們常常會呼叫最基本的初始化函式,來節省我們開發的時間。但在 Swift 中,將初始化分為 Designated Initializer 和 Convenience Initializer,使用方法如何呢?讓我們直接來實際看例子來做一個簡單的教學。

舉例來說,我們如果有一個物件叫做 Site,一個網站總不可能沒有網址和名稱吧?所以這個物件定義了 URL 和 Name 這兩個變數。

class Site{
    var name: String?
    var url:  String?
}

接下來,我們加入了初始化的元素,這時編輯器就出錯了,會出現「Designated initializer for ‘Site’ cannot delegate (with ‘self.init’); did you mean this to be a convenience initializer?」。

class Site{
    var name: String?
    var url:  String?

    init(name: String, url: String) {
        self.name = name
        self.url = url
    }
    
    init(url: String) {//錯誤
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

如果依 XCode 的建議來修改,改成以下,就又會好了!

class Site{
    var name: String?
    var url:  String?
    
    init(name: String, url: String) {
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) { 
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

這是怎麼回事呢?充滿了黑人問號啊!原來是因為當我們加了 Convenience 這個關鍵字後,初始化的函式才會變成 Convenience Initializer 函式,也才可以使用「self.init」去呼叫 Designated Initializer 的函式,而如果什麼都沒有加的初始化函式,就是 Designated Initializer,所以以原本的例子為例,Designated Initializer 和 Convenience Initializer 分別如下:

class Site{
    var name: String?
    var url:  String?
    
    init(name: String, url: String) { //Designated Initializer
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) { //Convenience Initializer
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

再來看看這個例子,如果我們使用 Convenience Initializer 來初始化變數,為什麼會錯呢?因為在 Convenience Initializer 設定變數值之前,都必須先呼叫  Designated Initializer 函式才可以喔!

class Site{
    var name: String?
    var url:  String?
    
    init(name: String, url: String) { //Designated Initializer
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) { //Convenience Initializer
        self.url = url
    }
}

如果 Class 所有的變數初始化後,Class 會自動產生一個 Designated Initializer 的 init  函式,而每一個 Class 內都至少必須要有一個 Designated Initializer 函式。所以如果我們將初始化的函式通通設為 Convenience Initializer,卻沒有將所有變數初始化,這時因為在 Class 內沒有 Designated Initializer,所以這時編繹器又會出現錯誤了喔

class Site{
    var name: String  //錯誤
    var url:  String  //錯誤
    
    convenience init(name: String, url: String) {
        self.init() //錯誤
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) {
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

解決方式就是我們要在建立變數時就將 name 和 url 初始化就可以了。

class Site{
    var name: String = "海芋小站"
    var url:  String = "https://www.inote.tw"
    
    convenience init(name: String, url: String) {
        self.init()
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) {
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

或者直接寫一個 Designated Initializer 的 init  函式。

class Site{
    var name: String
    var url:  String
    
    init() {
        self.name = "海芋小站"
        self.url  = "https://www.inote.tw"
    }
    
    convenience init(name: String, url: String) {
        self.init()
        self.name = name
        self.url = url
    }
    
    convenience init(url: String) {
        self.init(name: "海芋小站", url: url)
        self.url = url
    }
}

蘋果官方對於 Designated Initializer 和 Convenience Initializer 提出了三條規則。

Designated Initializer 可以呼叫它父類別的 Designated Initializer。

Rule 1
A designated initializer must call a designated initializer from its immediate superclass.

Convenience Initializer 可以呼叫其它 Convenience Initializer 。

Rule 2
A convenience initializer must call another initializer from the same class.

Convenience Initializer 最終必須呼叫 Designated Initializer。

Rule 3
A convenience initializer must ultimately call a designated initializer.

在蘋果官方網站中,有一張圖有很清楚的解釋,在設計程式時,你可以思考一下你的程式流程,必須要符合以下的規範喔!
swift-initializerDelegation02_2x

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments