在寫程式中,初始化 (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.
在蘋果官方網站中,有一張圖有很清楚的解釋,在設計程式時,你可以思考一下你的程式流程,必須要符合以下的規範喔!