前言
前一篇章大抵對值型別的結構做了一些討論,並實作結構在建構器代理的方法。並於文章尾段描摹類別的建構器的大概運作理論,本篇將著重於指定建構器、便利建構器兩者在類別中之父類、子類的運作之相對關係進行討論。
正文
類別的建構器三規則
誠如上一篇在談論指定建構器、便利建構器兩者的差別時,曾提及指定建構器會隨著父類別的建構器而繼承至子類別的使用中,便利建構器的關係則為次要、輔助性的補足。
然而,主要、次要兩者間是指定建構器之於便利建構器之間的關係,這件事情也因此導出Swift對於在父類別、子類別之繼承關係下,所建構出的三個對於指定建構器、便利建構器之間的規則:
A designated initializer must call a designated initializer from its immediate superclass.(指定建構器必須呼叫在它父類別中的指定建構器)
A convenience initializer must call another initializer from the same class.(便利建構器必須呼叫其他在同個類別中的建構器)
A convenience initializer must ultimately call a designated initializer.(便利建構器必須最終以呼叫一個指定建構器結束。)

總之,上圖大概可以導引出一個相對關係:
指定建構器是垂直的,是父子關係的,子類別中的建構器會承繼自父類別;便利建構器則是橫向的,是補充性、輔助性的,且受制於同個類別的。
而若要一言蔽之,指定建構器(Designated initializers)與便利建構器(Convenience initializers)的關係是這樣的:
- Designated initializers must always delegate up.(指定建構器必須總是向上代理)
- Convenience initializers must always delegate across.(便利建構器必須總是橫向代理)
以下為解釋指定建構器與便利建構器之關係,以下圖做例:

繼而在上圖中,我們稍微能窺見一件事:
最上層的指定建構器被一個便利建構器呼叫,且又有另一建構器呼叫其。這合於指定建構器、便利建構器之2、3兩規則的描述。
而以下子類別中,亦遵循三者所及的建構器規則描述。
類別建構過程的雙段邏輯
Class initialization in Swift is a two-phase process.
1.In the first phase, each stored property is assigned an initial value by the class that introduced it. (第一個階段,每個儲存型屬性通過引入它們的類別的建構器來設置初始值。)
Once the initial state for every stored property has been determined,(當每一個儲存型屬性值被確定後,)
2.the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.(第二階段開始,它給每個類別一次機會在新實例準備使用之前進一步定制它們的儲存型屬性)
在上述的描述下,我們可以知道一件事情:類別在第一階段是準備初始值的,而所謂的儲存屬性值被確定後,我們才會開始執行第二階段,也就是在要生成實體前有另一個機會再訂製儲存型屬性。然後才是生成實體。
就以官方網站之相關翻譯,大抵是這樣描述:
階段 1
- 某個指定建構器或便利建構器被呼叫;
- 完成新實例內存的分配,但此時內存還沒有被初始化;
- 指定建構器確保其所在類別引入的所有儲存型屬性都已賦初值。儲存型屬性所屬的內存完成初始化;
- 指定建構器將呼叫父類別的建構器,完成父類別屬性的初始化;
- 這個呼叫父類別建構器的過程沿著建構器鏈一直往上執行,直到到達建構器鏈的最頂部;
- 當到達了建構器鏈最頂部,且已確保所有實例包含的儲存型屬性都已經賦值,這個實例的內存被認為已經完全初始化。此時階段1完成。
類別在執行的時候,像是連動在一起的,建構器被呼叫,從子類一直上到父類的最頂端,然後,確認所有儲存性屬性都已賦值,從此確認初始化完成。
階段 2
- 從頂部建構器鏈一直往下,每個建構器鏈中類別的指定建構器都有機會進一步定制實例。建構器此時可以存取
self
、修改它的屬性並呼叫實例方法等等。 - 最終,任意建構器鏈中的便利建構器可以有機會定制實例和使用
self
。
接著是接著處理額外的賦值、修改屬性等工作,以讓屬性修改、呼叫等工作接著進展,最後才是所謂的定制實例工作。
指定建構器、便利建構器
不過,就以實際上而言,指定建構器、便利建構器的實作仍舊是另一回事:

以上這個例子,提出兩個建構器,其一為name建構器,其二為一便利建構器。從上述的例子來說,便利建構器為Human這個實體提供了預設值“Unknown”。
Human類別並沒有父類別,所以init(name:String)建構器不需要呼叫super.init來完成建構。
Human類別同樣提供了一個沒有參數的便利建構器init( )。
這個 init( ) 建構器為新名字提供了一個預設的占位名字,通過代理呼叫同一類別中定義的指定建構器 init(name:String) 並給參數 name 值 ”Unknown” 。

而子繼承Man則提供了兩個建構器:指定建構器、便利建構器兩者
在該指定建構器內,將新增的gender初始化,但未賦值,並將父類別的name用super.init( )繼承下來
在下面的便利建構器,則對於目前的子繼承,提供以之前name的內容,gender則是在未賦值前,提供以“Male”

而第三個子繼承Adam,則又新增了不同的屬性,如eyeOpen,然後依樣畫葫蘆,指定建構器繼承自上一父類別,便利建構器提供未賦值前的值。

當然,我們也可以賦予它值,就如上圖這樣。
不過,在指定建構器、便利建構器大抵上如上面描述的運作,不論是原理或是實例,卻仍有一點沒有敘述,即假若今日子類別中沒有任何的指定建構器呢?
官方網站語法指南指出:
子類別不會預設繼承父類別的建構器。但是如果特定條件可以滿足,父類別建構器是可以被自動繼承的。在實踐中,這意味著對於許多常見場景你不必重載父類別的建構器,並且在盡可能安全的情況下以最小的代價來繼承父類別的建構器。
假設要為子類別中引入的任意新屬性提供預設值,請遵守以下2個規則:
規則 1
如果子類別沒有定義任何指定建構器,它將自動繼承所有父類別的指定建構器。
規則 2
如果子類別提供了所有父類別指定建構器的實作 — 不管是通過規則1繼承過來的,還是通過自定義實作的 — 它將自動繼承所有父類別的便利建構器。
即使你在子類別中添加了更多的便利建構器,這兩條規則仍然適用。
於是,我們從這幾篇對於參考型別下,類別的繼承有了更深一層的認識,類別在運作上,會一層一層的往上溯源,接下再往下賦值的運作邏輯,而就此邏輯而言,指定建構器的主要繼承、便利建構器的輔助補足,則在其中最為類別繼承中,有較為複雜的概念,日後實作過程,若有未釐清之概念,仍會於此篇章後,提出描述。