Initialization

在 Swift 中,Initialization 的重點就圍繞著一句話:

Every value must be initialized before it is used

以下的範例在 compile-time 時就會出錯,因為 message 有可能會沒有被初始化 :

var message: String

if sessionStarted {
   message = "Welcome to Intermediate Swift!"
}

println(message)
// error: variable 'message' used before being initialized

改成這樣就可以了,每個情況下 message 都會被初始化:

var message: String

if sessionStarted {
   message = "Welcome to Intermediate Swift!"
} else {
   message = "See you next year!"
}

println(message)

而 Swift 中,complier 會幫我們檢查所有可能發生的情況。如果有未初始化的情況,就會顯示 error ,這大大降低了我們寫程式出錯的可能!

Structure Initialization

我們先來看 structure 的 initializer,假設我們有一個 Color 的 structure,我們給他一個 initializer,init(grayScale: Double)

struct Color {
   let red, green, blue: Double
  init(grayScale: Double) {
    red = grayScale
    green = grayScale
    blue = grayScale
  }
}

但如果我們忘了給 red 一個值,則會出現 comiple-error:

struct Color {
   let red, green, blue: Double
   init(grayScale: Double) {
  
       green = grayScale
       blue = grayScale
   }
}
// error: variable 'self.red' used before being initialized

這樣寫起來安全多了,不用擔心少寫讓某個 properties 初始化!


另外假設我們有一個 validateColor() 的 method,要使用時,一定要在所有的 properties 初始化完之後才能使用:

struct Color {
  let red, green, blue: Double
  mutating func validateColor() { ... }
  init(grayScale: Double) {
    red = grayScale
    green = grayScale
    validateColor()
    blue = grayScale
  }
}
// error: 'self' used before being initialized

以上的程式會出錯,因為我們還沒初始化 blue,所以不能呼叫任何 method。只要把 validateColor() 移到最下面即可:

struct Color {
  let red, green, blue: Double
  mutating func validateColor() { ... }
  init(grayScale: Double) {
    red = grayScale
    green = grayScale
    blue = grayScale
    validateColor()
  }
}

Memberwise Initializers

在 structure 還有一個特別的地方,就是有 memberwise initializers,以下為一個 Color 的 structure:

struct Color {
  let red, green, blue: Double
}

我們不用宣告 initializer,就有 memberwise initializers 可以用了。也就是說,我們可以直接呼叫 Color(red: 1.0, green: 0.0, blue: 1.0)

struct Color {
  let red, green, blue: Double
}

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

這是 structure 才有的特性,很方便!

Default Value

如果不寫 initializer 的話,也可以直接用 default value,就不用另外透過 initializer 去設定 properties 的值了:

struct Color {
   let red = 0.0, green = 0.0, blue = 0.0
}

let black = Color()
// black is (0.0, 0.0, 0.0)

其中 Color() 為 default initializer。

Class Initialization

Class 的 initialization 方式如下:

class Car {
   var paintColor: Color
  
   init(color: Color) {
       paintColor = color
   }
}

class RaceCar: Car {
  var hasTurbo: Bool
  
  init(color: Color, turbo: Bool) {
      hasTurbo = turbo
      super.init(color: color)
  }
}

其中可以注意到,有一個地方跟 Objective-C 有點不太一樣,hasTurbo = turbo 先,再來才是 super.init(color: color)

hasTurbo = turbo
super.init(color: color)

以前在 Objective-C 是先呼叫 superclass 的 initializer,再初始化自己的 properties,但是在 Swift 剛好相反。這是因為:「如果在 Swift 先呼叫 superclass 的 initializer 會有安全性的問題」。

為什麼請看以下的範例:


假設我們有 Car classRaceCar 繼承 Car,並且 RaceCar 複寫 Car 裡的 fillGasTank()

class Car {
   var paintColor: Color
   func fillGasTank() {...}
       init(color: Color) {
       paintColor = color
       fillGasTank()
   }
}

class RaceCar: Car {
  var hasTurbo: Bool
  override func fillGasTank() { ... }
  init(color: Color, turbo: Bool) {
      super.init(color: color)
      hasTurbo = turbo
  }
}

如果我們在 RaceCarinit(...) 裡先呼叫 super.init(...),就會造成 未初始化 properties 就使用 properties 的問題。為什麼呢?以下的流程圖解釋:

Screen Shot 2014-07-17 at 4.52.58 PM.png

  1. RaceCar 中呼叫 super.init(color: color) 會呼叫 Carinit(...)
  2. Car 中的 init(...) 會呼叫 fillGasTank()
  3. 因為 fillGasTank() 是被 複寫 (override) 的,所以會呼叫 RaceCarfillGasTank()

此時,因為我們還沒有初始化 RaceCar 自己的 properties,所以呼叫 fillGasTank() 會出現 compile-time error。

所以在 Swift 中,要先初始化自己的 properties,才能呼叫 superclass 的 initializer。


所以只要把 superclass 的 initializer 放到後面可以了:

class Car {
   var paintColor: Color
   func fillGasTank() {...}
       init(color: Color) {
       paintColor = color
       fillGasTank()
   }
}

class RaceCar: Car {
  var hasTurbo: Bool
  override func fillGasTank() { ... }
  init(color: Color, turbo: Bool) {
      hasTurbo = turbo
      super.init(color: color)
  }
}

Initializer Delegation

在 Swift 中,initializer 有分 Designated InitializerConvenience 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()

class RaceCar: Car {
   var hasTurbo: Bool
   init(color: Color, turbo: Bool) {
       hasTurbo = turbo
       super.init(color: color)
   }

   convenience init(color: Color) {
       self.init(color: color, turbo: true)
  }

  convenience init() {
      self.init(color: Color(gray: 0.4))
  }
}

到目前為止,我們發現,initializers 之間會有一些關連,而 initializer 的屬性影響到 initializer 之間的關係。

我們用圖解來看:

Screen Shot 2014-07-18 at 11.19.06 AM.png

在 Swift 中,designate initializer 負責呼叫 superclass 的 initializer,並且不會呼叫其他的 initializer。而 convenience initializer 只會呼叫自己 class 裡面的 designate initializer。這就是標準的 initializers 之間的關係。

Initializer Inheritance

在 Swift 中,一般的情況下,subclass 並 不會 繼承 superclass 的 initializers。只有符合「特定」的情況,才會自動的繼承 superclass 的 initializers。

舉一個會例子:

假設 FormulaOne 繼承的 RaceCarFormulaOne 有自己的 property minimumWeight

class FormulaOne: RaceCar {
   let minimumWeight
}

在這種情況下,FormulaOne 並不會繼承 RaceCar 的 initializers,因為 minimumWeight 沒有被初始化。


但是如果給 minimumWeight 初始值:

class FormulaOne: RaceCar {
   let minimumWeight = 642
}

FormulaOne 會繼承 RaceCar 的 initializers。因為 minimumWeight 已經被初始化了,是安全的,因此就會自動繼承 RaceCar 的 initializers,也就是繼承下面這些 initializers:

init(color: Color, turbo: Bool) {
   hasTurbo = turbo
   super.init(color: color)
}
convenience init(color: Color) {
   self.init(color: color, turbo: true)
}
convenience init() {
   self.init(color: Color(gray: 0.4))
}

但是我們不想要繼承這些 initializers,因為我們發現 FormulaOne 在一般的狀況下是不能有 turbo 的,因此正確的 initializer 應該是 self.init(color: color, turbo: false)

class FormulaOne: RaceCar {
   let minimumWeight = 642
   init(color: Color) {
       super.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 我要晚一點才初始化」:

class Game {
   var singlePlayer: Player?
  @lazy var multiplayerManager = MultiplayerManager
   func beginGameWithPlayers(players: Player...) {
       if players.count == 1 {       
           singlePlayer = players[0]
       } else {
       for player in players {
           multiplayerManager.addPlayer(player)
      }
}

Deinitialization

在 Swift 中,有 initialization,當然也有 deinitialization。但是通常我們都不需要自己實作 deinitializer。

  1. 因為 Swift 是 ARC,會自動釋放 memory,所以大部分的時間我們不需要在 deninitializer 做事情。
  2. Swift 會自動幫你呼叫 deinitializer,就算你沒有寫也會呼叫預設的。

當然,有時候我們還是要利用 deinitializer,舉一個例子:檔案存取時,我們要關掉檔案的 I/O,此時就需要在 deinitialer裡做一些事情。Deinitializer 在 Swift 中的 keyword 為 deinit,例如:

class FileHandle {
   let fileDescriptor: FileDescriptor
   init(path: String) {
       fileDescriptor = openFile(path)
   }
   deinit {
       closeFile(fileDescriptor)
   }
}

小結

有關 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 一直都存在,如果你需要特別操作時,再去使用它。