Initialization
在 Swift 中,Initialization 的重點就圍繞著一句話:
Every value must be initialized before it is used
以下的範例在 compile-time 時就會出錯,因為 message
有可能會沒有被初始化 :
改成這樣就可以了,每個情況下 message
都會被初始化:
而 Swift 中,complier 會幫我們檢查所有可能發生的情況。如果有未初始化的情況,就會顯示 error ,這大大降低了我們寫程式出錯的可能!
Structure Initialization
我們先來看 structure 的 initializer,假設我們有一個 Color
的 structure,我們給他一個 initializer,init(grayScale: Double)
:
但如果我們忘了給 red
一個值,則會出現 comiple-error:
這樣寫起來安全多了,不用擔心少寫讓某個 properties 初始化!
另外假設我們有一個 validateColor()
的 method,要使用時,一定要在所有的 properties 初始化完之後才能使用:
以上的程式會出錯,因為我們還沒初始化 blue
,所以不能呼叫任何 method。只要把 validateColor()
移到最下面即可:
Memberwise Initializers
在 structure 還有一個特別的地方,就是有 memberwise initializers,以下為一個 Color
的 structure:
我們不用宣告 initializer,就有 memberwise initializers 可以用了。也就是說,我們可以直接呼叫 Color(red: 1.0, green: 0.0, blue: 1.0)
:
這是 structure 才有的特性,很方便!
Default Value
如果不寫 initializer 的話,也可以直接用 default value,就不用另外透過 initializer 去設定 properties 的值了:
其中 Color()
為 default initializer。
Class Initialization
Class 的 initialization 方式如下:
其中可以注意到,有一個地方跟 Objective-C 有點不太一樣,hasTurbo = turbo
先,再來才是 super.init(color: color)
:
以前在 Objective-C 是先呼叫 superclass 的 initializer,再初始化自己的 properties,但是在 Swift 剛好相反。這是因為:「如果在 Swift 先呼叫 superclass 的 initializer 會有安全性的問題」。
為什麼請看以下的範例:
假設我們有 Car
class,RaceCar
繼承 Car
,並且 RaceCar
複寫 Car
裡的 fillGasTank()
:
如果我們在 RaceCar
的 init(...)
裡先呼叫 super.init(...)
,就會造成 未初始化 properties 就使用 properties 的問題。為什麼呢?以下的流程圖解釋:
- 在
RaceCar
中呼叫super.init(color: color)
會呼叫Car
的init(...)
。 - 在
Car
中的init(...)
會呼叫fillGasTank()
。 - 因為
fillGasTank()
是被 複寫 (override) 的,所以會呼叫RaceCar
的fillGasTank()
。
此時,因為我們還沒有初始化 RaceCar
自己的 properties,所以呼叫 fillGasTank()
會出現 compile-time error。
所以在 Swift 中,要先初始化自己的 properties,才能呼叫 superclass 的 initializer。
所以只要把 superclass 的 initializer 放到後面可以了:
Initializer Delegation
在 Swift 中,initializer 有分 Designated Initializer 與 Convenience Initializers。
Designated Initializer
在 Swift 中,最主要的 initializer 叫做 Designated Initializer。用上面的例子來說 RaceCar
的 designated initializer 為 init(color: Color, turbo: Bool)
,如果這個 class 有繼承的其他 class 的話,通常在會 designate initialzer 中呼叫 superclass 的 initializer。
Convenience Initializers
而除了 designated initializer 之外,還有 Convenience Initializers,也就是一個 簡單 的 initializer,通常都是不用傳太多參數,或者是可以設定預設值的 initializer。而 convenience initializer 會呼叫 designated initilizer。
在 Swift 中,convenience initializers 可以在宣告之前加入 keyword:convenience
。
以下範例中,RaceCar
有兩個 convenience initializers:convenience init(color: Color)
與 convenience init()
:
到目前為止,我們發現,initializers 之間會有一些關連,而 initializer 的屬性影響到 initializer 之間的關係。
我們用圖解來看:
在 Swift 中,designate initializer 負責呼叫 superclass 的 initializer,並且不會呼叫其他的 initializer。而 convenience initializer 只會呼叫自己 class 裡面的 designate initializer。這就是標準的 initializers 之間的關係。
Initializer Inheritance
在 Swift 中,一般的情況下,subclass 並 不會 繼承 superclass 的 initializers。只有符合「特定」的情況,才會自動的繼承 superclass 的 initializers。
舉一個會例子:
假設 FormulaOne
繼承的 RaceCar
,FormulaOne
有自己的 property minimumWeight
。
在這種情況下,FormulaOne
並不會繼承 RaceCar
的 initializers,因為 minimumWeight
沒有被初始化。
但是如果給 minimumWeight
初始值:
FormulaOne
會繼承 RaceCar
的 initializers。因為 minimumWeight
已經被初始化了,是安全的,因此就會自動繼承 RaceCar
的 initializers,也就是繼承下面這些 initializers:
但是我們不想要繼承這些 initializers,因為我們發現 FormulaOne
在一般的狀況下是不能有 turbo
的,因此正確的 initializer 應該是 self.init(color: color, turbo: false)
:
要注意的是,如果自己實作了 initializer,就不會繼承任何 superclass 的 initializers 了。
Lazy Properties
有時候,我們不想要在一開始的時候就初始化 properties,我們希望當使用者「用到」的時候再初始化,也就是常見的 lazy loading。
在 Swift 中,提供了 keyword @lazy
,讓 compiler 知道這個 property 可以晚一點在初始化。
假設,我們有一個 Game
的 class,這個 Game
有分 單機版(singlePlayer
) 與 多人連線 (multiplayerManager),但是要等到遊戲開始的時候我們才會知道。因此,我們幫 multiplayerManager
加上 @lazy
,通知 compiler:「multiplayerManager
我要晚一點才初始化」:
Deinitialization
在 Swift 中,有 initialization,當然也有 deinitialization。但是通常我們都不需要自己實作 deinitializer。
- 因為 Swift 是 ARC,會自動釋放 memory,所以大部分的時間我們不需要在 deninitializer 做事情。
- Swift 會自動幫你呼叫 deinitializer,就算你沒有寫也會呼叫預設的。
當然,有時候我們還是要利用 deinitializer,舉一個例子:檔案存取時,我們要關掉檔案的 I/O,此時就需要在 deinitialer裡做一些事情。Deinitializer 在 Swift 中的 keyword 為 deinit
,例如:
小結
有關 Swift 中的 initialization,可以歸納出以下一些結論:
- Initialize all values before you use them.
- Set all stored properties first, then call super.init.
- 在使用任何 properties 之前,要先初始化它。
- 先初使化所有的 stored properties 之後,再呼叫 super.init.
- Designated initializers only delegate up.
- Convenience initializers only delegate across.
- Deisgnated initializers 用來呼叫 superclass 的 initializer。
- Convenience initializers 用來呼叫 同一個 class 的 designated initialzer。
Deisgnated initializers 與 Convenience initializers 不要混用了!
- Deinitializers are there… if you need them.
- Deinitialiers 一直都存在,如果你需要特別操作時,再去使用它。