Swift Initialization (3) — Two-Phase Initialization

Racing Wang
17LIVE Tech Insight
6 min readAug 1, 2020

Swift Initialization (1) — Default Initializer

Swift Initialization (2) — Initializer Delegation

Swift Initialization (3) — Two-Phase Initialization

大家在剛接觸 Swift 時,是否碰過在 Initializer A 中參照 self(讀寫 Stored Property、呼叫 Instance Method⋯等)編譯失敗,但在 Initializer B 中參照 self 卻能編譯成功的情況?又或者是在同一個 Initializer 中 A 行參照 self 會編譯失敗,而改到 B 行卻又編譯成功。如果你至今仍認為這是 Swift 七大不思議之謎,那麼你就需要先了解 Initialization 的過程其實包含兩個階段,它們各自負責不同的任務,這也影響了可以參照 self 的時機。

編譯器的四道安全檢查

首先,我們來瞧瞧 Swift 編譯器針對 Two-Phase Initialization 的檢查規則:

安全檢查一

Designated Initializer 在向上委派 Superclass Initializer 之前,必須要確保自身宣告的 Stored Property 皆已完成首次賦值。

還記得在第一篇文章開頭我們強調到 Initializer 的使命是什麼嗎?(這裡指的是在該類別宣告的 Stored Property,不包含 Inherited Property)

安全檢查二

Designated Initializer 在向上委派前不能存取 Inherited Property。

向上委派結束後,意味著所有的 Stored Property(包含 Inherited Property)都已完成首次賦值,因此可以開始安心地存取它們了。

安全檢查三

Convenience Initializer 在橫向委派前不能存取任何 Property。

同樣地,橫向委派結束也意味著所有的 Stored Property 首次賦值完成,由於只有 Designated Initializer 才能進行首次賦值,Convenience Initializer 勢必要等到委派結束後才能存取所有 Property。

安全檢查四

Initializer 在委派結束前,不能呼叫 Instance Method、讀取或修改 Stored Property,或是進行任何將 self 當 Value 傳遞的行為。

簡單來說就是除了首次賦值外不能進行任何參照 self 的行為。

Initialization Phase 1

Phase 1 的主要任務為初始化記憶體,過程如下:

  1. 任一 Initializer 被呼叫。
  2. 這個實體(Instance)的記憶體被配置(Allocated)出來,但是記憶體尚未被初始化。
  3. Designated Initializer 確認自身宣告的 Stored Property 完成首次賦值,現在這些 Property 初始化完成了。
  4. Designated Initializer 接著向上委派給 Superclass Initializer 進行同樣的動作。
  5. 這樣的動作一路完成直至 Root Class。
  6. 等到 Root Class 也完成首次賦值後,意味著所有的 Stored Property 都已經有初始值,這塊記憶體位置正式完成初始化,而 Phase 1 也宣告結束。
Phase 1 流程圖

Initialization Phase 2

Phase 2 的主要任務為進行實體的客製化,過程如下:

  1. 從 Root Class 完成最後一個 Stored Property 首次賦值的下一行程式碼起,正式進入 Phase 2。此時可以盡情地參照 self、呼叫 Method、修改所有 Property 了!
  2. 客製化的過程一路回到第一個 Designated Initializer,終於 — 輪到 Convenience Initializer 可以進行客製化了,而所有客製化完成也代表著 Phase 2 的結束。
Phase 2 流程圖

Exercise

仔細看看下面這段程式碼,編譯是否會成功呢?

答案是會的。

你也許會這樣想:行 14 在 Phase 1 完成前呼叫了 Instance Method,但別忘了首次賦值有兩種方式,一種是在宣告時給予預設值,另一種才是在 Initializer 中賦值message 在宣告時已經將空字串設為預設值,因此行 14 其實已經完成了所有 Stored Property 的首次賦值,開始進入 Phase 2,可以安心地參照 self 了。

看到這裏,有沒有覺得 Two-Phase Initialization 的過程似曾相識?沒錯,事實上 Objective-C 也是同樣的初始化方式,只是 Objective-C 在宣告 Property 時皆會給予預設值 0nil,因此 Phase 1 便已經隱含地完成了,後續對 Property 的設置其實都是 Phase 2。

- (instancetype)init {
if (self = [super init]) { // Phase 1 complete
// Phase 2 begins
_title = "Hello World!"
_message = "Use your force."
}
}

Two-Phase Initialization 就介紹到這,接下來的幾篇文章會對 Class Inheritance and Initialize 以及其它幾種 Initializer 進行說明,盡請期待!

--

--