Swift3.0-構(gòu)造器

構(gòu)造過程

構(gòu)造過程是使用類昵宇、結(jié)構(gòu)體或枚舉類型的實例之前的準備過程黔酥。在新實例可用前必須執(zhí)行這個過程,具體操作包括設置實例中每個存儲型屬性的初始值和執(zhí)行其他必須的設置或初始化工作。

通過定義構(gòu)造器(Initializers)來實現(xiàn)構(gòu)造過程碧库,這些構(gòu)造器可以看做是用來創(chuàng)建特定類型新實例的特殊方法盅粪。與 Objective-C 中的構(gòu)造器不同钓葫,Swift 的構(gòu)造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化票顾。

存儲屬性的初始賦值

類和結(jié)構(gòu)體在創(chuàng)建實例時础浮,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處于一個未知的狀態(tài)奠骄。

你可以在構(gòu)造器中為存儲型屬性賦初值豆同,也可以在定義屬性時為其設置默認值。

注意

當你為存儲型屬性設置默認值或者在構(gòu)造器中為其賦值時含鳞,它們的值是被直接設置的影锈,不會觸發(fā)任何屬性觀察者(property observers)。

構(gòu)造器

構(gòu)造器在創(chuàng)建某個特定類型的新實例時被調(diào)用。它的最簡形式類似于一個不帶任何參數(shù)的實例方法精居,以關鍵字init命名:

init() {
    // 在此處執(zhí)行構(gòu)造過程
}

例子:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 輸出 "The default temperature is 32.0° Fahrenheit”
默認屬性值

如前所述屯耸,你可以在構(gòu)造器中為存儲型屬性設置初始值慕趴。同樣,你也可以在屬性聲明時為其設置默認值。

注意
如果一個屬性總是使用相同的初始值拔创,那么為其設置一個默認值比每次都在構(gòu)造器中賦值要好兰怠。兩種方法的效果是一樣的克蚂,只不過使用默認值讓屬性的初始化和聲明結(jié)合得更緊密俱诸。使用默認值能讓你的構(gòu)造器更簡潔、更清晰维雇,且能通過默認值自動推導出屬性的類型淤刃;同時,它也能讓你充分利用默認構(gòu)造器吱型、構(gòu)造器繼承等特性(后續(xù)章節(jié)將講到)逸贾。

你可以使用更簡單的方式在定義結(jié)構(gòu)體Fahrenheit時為屬性temperature設置默認值:

struct Fahrenheit {
    var temperature = 32.0
}

構(gòu)造參數(shù)

自定義構(gòu)造過程時,可以在定義中提供構(gòu)造參數(shù)津滞,指定所需值的類型和名字铝侵。構(gòu)造參數(shù)的功能和語法跟函數(shù)和方法的參數(shù)相同。

struct Fahrenheit {
    var temperature: Double
    init(temperature:Double) {
        self.temperature = temperature
    }
}
可選屬性類型

如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性——無論是因為它無法在初始化時賦值触徐,還是因為它在之后某個時間點可以賦值為空——你都需要將它定義為可選類型(optional type)咪鲜。可選類型的屬性將自動初始化為nil撞鹉,表示這個屬性是有意在初始化時設置為空的疟丙。

構(gòu)造過程中常量屬性的修改

你可以在構(gòu)造過程中的任意時間點給常量屬性指定一個值,只要在構(gòu)造過程結(jié)束時是一個確定的值鸟雏。一旦常量屬性被賦值享郊,它將永遠不可更改。

注意
對于類的實例來說崔慧,它的常量屬性只能在定義它的類的構(gòu)造過程中修改拂蝎;不能在子類中修改。

你可以修改上面的SurveyQuestion示例惶室,用常量屬性替代變量屬性text,表示問題內(nèi)容textSurveyQuestion的實例被創(chuàng)建之后不會再被修改玄货。盡管text屬性現(xiàn)在是常量皇钞,我們?nèi)匀豢梢栽陬惖臉?gòu)造器中設置它的值:

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 輸出 "How about beets?"
默認構(gòu)造器

如果結(jié)構(gòu)體或類的所有屬性都有默認值,同時沒有自定義的構(gòu)造器松捉,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器(default initializers)夹界。這個默認構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設置為默認值的實例。

下面例子中創(chuàng)建了一個類ShoppingListItem隘世,它封裝了購物清單中的某一物品的屬性:名字(name)可柿、數(shù)量(quantity)和購買狀態(tài) purchase state

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()
結(jié)構(gòu)體的逐一成員構(gòu)造器

除了上面提到的默認構(gòu)造器鸠踪,如果結(jié)構(gòu)體沒有提供自定義的構(gòu)造器,它們將自動獲得一個逐一成員構(gòu)造器复斥,即使結(jié)構(gòu)體的存儲型屬性沒有默認值营密。

逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實例里成員屬性的快捷方法。我們在調(diào)用逐一成員構(gòu)造器時目锭,通過與成員屬性名相同的參數(shù)名進行傳值來完成對成員屬性的初始賦值评汰。

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
值類型的構(gòu)造器代理

構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理痢虹,它能減少多個構(gòu)造器間的代碼重復被去。

構(gòu)造器代理的實現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承奖唯,所以構(gòu)造器代理的過程相對簡單惨缆,因為它們只能代理給自己的其它構(gòu)造器。類則不同丰捷,它可以繼承自其它類坯墨,這意味著類有責任保證其所有繼承的存儲型屬性在構(gòu)造時也能正確的初始化。

對于值類型瓢阴,你可以使用self.init在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器畅蹂。并且你只能在構(gòu)造器內(nèi)部調(diào)用self.init

如果你為某個值類型定義了一個自定義的構(gòu)造器荣恐,你將無法訪問到默認構(gòu)造器(如果是結(jié)構(gòu)體液斜,還將無法訪問逐一成員構(gòu)造器)。這種限制可以防止你為值類型增加了一個額外的且十分復雜的構(gòu)造器之后,仍然有人錯誤的使用自動生成的構(gòu)造器

注意
假如你希望默認構(gòu)造器叠穆、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實例少漆,可以將自定義的構(gòu)造器寫到擴展(extension)中,而不是寫在值類型的原始定義中硼被。

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}
指定構(gòu)造器和便利構(gòu)造器

指定構(gòu)造器(designated initializers)是類中最主要的構(gòu)造器示损。一個指定構(gòu)造器將初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類的構(gòu)造器來實現(xiàn)父類的初始化嚷硫。

每一個類都必須擁有至少一個指定構(gòu)造器检访。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個條件仔掸。

便利構(gòu)造器(convenience initializers)是類中比較次要的脆贵、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個類中的指定構(gòu)造器起暮,并為其參數(shù)提供默認值卖氨。你也可以定義便利構(gòu)造器來創(chuàng)建一個特殊用途或特定輸入值的實例。

你應當只在必要的時候為類提供便利構(gòu)造器,比方說某種情況下通過使用便利構(gòu)造器來快捷調(diào)用某個指定構(gòu)造器筒捺,能夠節(jié)省更多開發(fā)時間并讓類的構(gòu)造過程更清晰明了柏腻。

指定構(gòu)造器和便利構(gòu)造器的語法

類的指定構(gòu)造器的寫法跟值類型簡單構(gòu)造器一樣:

init(parameters) {
    statements
}

便利構(gòu)造器也采用相同樣式的寫法,但需要在init關鍵字之前放置convenience關鍵字系吭,并使用空格將它們倆分開:

convenience init(parameters) {
    statements
}
類的構(gòu)造器代理規(guī)則

為了簡化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關系五嫂,Swift 采用以下三條規(guī)則來限制構(gòu)造器之間的代理調(diào)用:

規(guī)則 1

指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。

規(guī)則 2

便利構(gòu)造器必須調(diào)用同一類中定義的其它構(gòu)造器村斟。

規(guī)則 3

便利構(gòu)造器必須最終導致一個指定構(gòu)造器被調(diào)用贫导。

一個更方便記憶的方法是:

  • 指定構(gòu)造器必須總是向上代理
  • 便利構(gòu)造器必須總是橫向代理

這些規(guī)則可以通過下面圖例來說明:

構(gòu)造器代理圖
構(gòu)造器代理圖

如圖所示,父類中包含一個指定構(gòu)造器和兩個便利構(gòu)造器蟆盹。其中一個便利構(gòu)造器調(diào)用了另外一個便利構(gòu)造器孩灯,而后者又調(diào)用了唯一的指定構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3逾滥。這個父類沒有自己的父類峰档,所以規(guī)則 1 沒有用到。

子類中包含兩個指定構(gòu)造器和一個便利構(gòu)造器寨昙。便利構(gòu)造器必須調(diào)用兩個指定構(gòu)造器中的任意一個讥巡,因為它只能調(diào)用同一個類里的其他構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3舔哪。而兩個指定構(gòu)造器必須調(diào)用父類中唯一的指定構(gòu)造器欢顷,這滿足了規(guī)則 1。

注意
這些規(guī)則不會影響類的實例如何創(chuàng)建捉蚤。任何上圖中展示的構(gòu)造器都可以用來創(chuàng)建完全初始化的實例抬驴。這些規(guī)則只影響類定義如何實現(xiàn)。

下面圖例中展示了一種涉及四個類的更復雜的類層級結(jié)構(gòu)缆巧。它演示了指定構(gòu)造器是如何在類層級中充當“管道”的作用布持,在類的構(gòu)造器鏈上簡化了類之間的相互關系。

復雜構(gòu)造器代理圖
復雜構(gòu)造器代理圖
兩段式構(gòu)造過程

Swift 中類的構(gòu)造過程包含兩個階段陕悬。第一個階段题暖,每個存儲型屬性被引入它們的類指定一個初始值。當每個存儲型屬性的初始值被確定后捉超,第二階段開始胧卤,它給每個類一次機會,在新實例準備使用之前進一步定制它們的存儲型屬性拼岳。

兩段式構(gòu)造過程的使用讓構(gòu)造過程更安全灌侣,同時在整個類層級結(jié)構(gòu)中給予了每個類完全的靈活性。兩段式構(gòu)造過程可以防止屬性值在初始化之前被訪問裂问,也可以防止屬性被另外一個構(gòu)造器意外地賦予不同的值。

注意
Swift 的兩段式構(gòu)造過程跟 Objective-C 中的構(gòu)造過程類似。最主要的區(qū)別在于階段 1堪簿,Objective-C 給每一個屬性賦值0或空值(比如說0nil)痊乾。Swift 的構(gòu)造流程則更加靈活,它允許你設置定制的初始值椭更,并自如應對某些屬性不能以0nil作為合法默認值的情況哪审。

Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過程能不出錯地完成:

安全檢查 1

指定構(gòu)造器必須保證它所在類引入的所有屬性都必須先初始化完成虑瀑,之后才能將其它構(gòu)造任務向上代理給父類中的構(gòu)造器湿滓。

如上所述,一個對象的內(nèi)存只有在其所有存儲型屬性確定之后才能完全初始化舌狗。為了滿足這一規(guī)則叽奥,指定構(gòu)造器必須保證它所在類引入的屬性在它往上代理之前先完成初始化。

安全檢查 2

指定構(gòu)造器必須先向上代理調(diào)用父類構(gòu)造器痛侍,然后再為繼承的屬性設置新值朝氓。如果沒這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋主届。

安全檢查 3

便利構(gòu)造器必須先代理調(diào)用同一類中的其它構(gòu)造器赵哲,然后再為任意屬性賦新值。如果沒這么做君丁,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋枫夺。

安全檢查 4

構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實例方法绘闷,不能讀取任何實例屬性的值橡庞,不能引用self作為一個值。

類實例在第一階段結(jié)束以前并不是完全有效的簸喂。只有第一階段完成后毙死,該實例才會成為有效實例,才能訪問屬性和調(diào)用方法喻鳄。

以下是兩段式構(gòu)造過程中基于上述安全檢查的構(gòu)造流程展示:

階段 1
  • 某個指定構(gòu)造器或便利構(gòu)造器被調(diào)用扼倘。
  • 完成新實例內(nèi)存的分配,但此時內(nèi)存還沒有被初始化除呵。
  • 指定構(gòu)造器確保其所在類引入的所有存儲型屬性都已賦初值再菊。存儲型屬性所屬的內(nèi)存完成初始化。
  • 指定構(gòu)造器將調(diào)用父類的構(gòu)造器颜曾,完成父類屬性的初始化纠拔。
  • 這個調(diào)用父類構(gòu)造器的過程沿著構(gòu)造器鏈一直往上執(zhí)行,直到到達構(gòu)造器鏈的最頂部泛豪。
  • 當?shù)竭_了構(gòu)造器鏈最頂部稠诲,且已確保所有實例包含的存儲型屬性都已經(jīng)賦值侦鹏,這個實例的內(nèi)存被認為已經(jīng)完全初始化。此時階段 1 完成臀叙。
階段 2
  • 從頂部構(gòu)造器鏈一直往下略水,每個構(gòu)造器鏈中類的指定構(gòu)造器都有機會進一步定制實例。構(gòu)造器此時可以訪問self劝萤、修改它的屬性并調(diào)用實例方法等等渊涝。
  • 最終,任意構(gòu)造器鏈中的便利構(gòu)造器可以有機會定制實例和使用self床嫌。

下圖展示了在假定的子類和父類之間的構(gòu)造階段 1:

構(gòu)建過程階段1
構(gòu)建過程階段1

在這個例子中跨释,構(gòu)造過程從對子類中一個便利構(gòu)造器的調(diào)用開始。這個便利構(gòu)造器此時沒法修改任何屬性厌处,它把構(gòu)造任務代理給同一類中的指定構(gòu)造器鳖谈。

如安全檢查 1 所示,指定構(gòu)造器將確保所有子類的屬性都有值嘱蛋。然后它將調(diào)用父類的指定構(gòu)造器蚯姆,并沿著構(gòu)造器鏈一直往上完成父類的構(gòu)造過程。

父類中的指定構(gòu)造器確保所有父類的屬性都有值洒敏。由于沒有更多的父類需要初始化龄恋,也就無需繼續(xù)向上代理。

一旦父類中所有屬性都有了初始值凶伙,實例的內(nèi)存被認為是完全初始化郭毕,階段 1 完成。

以下展示了相同構(gòu)造過程的階段 2:

構(gòu)建過程階段2
構(gòu)建過程階段2

父類中的指定構(gòu)造器現(xiàn)在有機會進一步來定制實例(盡管這不是必須的)函荣。

一旦父類中的指定構(gòu)造器完成調(diào)用显押,子類中的指定構(gòu)造器可以執(zhí)行更多的定制操作(這也不是必須的)。

最終傻挂,一旦子類的指定構(gòu)造器完成調(diào)用乘碑,最開始被調(diào)用的便利構(gòu)造器可以執(zhí)行更多的定制操作。

構(gòu)造器的繼承和重寫

跟 Objective-C 中的子類不同金拒,Swift 中的子類默認情況下不會繼承父類的構(gòu)造器兽肤。Swift 的這種機制可以防止一個父類的簡單構(gòu)造器被一個更精細的子類繼承,并被錯誤地用來創(chuàng)建子類的實例绪抛。

注意
父類的構(gòu)造器僅會在安全和適當?shù)那闆r下被繼承资铡。

假如你希望自定義的子類中能提供一個或多個跟父類相同的構(gòu)造器,你可以在子類中提供這些構(gòu)造器的自定義實現(xiàn)幢码。

當你在編寫一個和父類中指定構(gòu)造器相匹配的子類構(gòu)造器時笤休,你實際上是在重寫父類的這個指定構(gòu)造器。因此症副,你必須在定義子類構(gòu)造器時帶上override修飾符店雅。即使你重寫的是系統(tǒng)自動提供的默認構(gòu)造器政基,也需要帶上override修飾符。

正如重寫屬性底洗,方法或者是下標腋么,override修飾符會讓編譯器去檢查父類中是否有相匹配的指定構(gòu)造器,并驗證構(gòu)造器參數(shù)是否正確亥揖。

注意
當你重寫一個父類的指定構(gòu)造器時,你總是需要寫override修飾符圣勒,即使你的子類將父類的指定構(gòu)造器重寫為了便利構(gòu)造器费变。

相反,如果你編寫了一個和父類便利構(gòu)造器相匹配的子類構(gòu)造器圣贸,由于子類不能直接調(diào)用父類的便利構(gòu)造器挚歧,因此,嚴格意義上來講吁峻,你的子類并未對一個父類構(gòu)造器提供重寫滑负。最后的結(jié)果就是,你在子類中“重寫”一個父類便利構(gòu)造器時用含,不需要加override前綴矮慕。

class A {
    var a: Int
    init(a: Int) {
        self.a = a
    }
    convenience init() {
        self.init(a: 1)
    }
}

class B: A { 
    override init(a: Int) {
        super.init(a: 2)
    }
    
    init() {
        super.init(a: 2)
    }
}

一般來說,子類的初始化順序是:

  1. 設置子類自己需要初始化的參數(shù)啄骇,power = 10
  2. 調(diào)用父類的相應的初始化方法痴鳄,super.init()
  3. 對父類中的需要改變的成員進行設定,name = "tiger"

其中第三步是根據(jù)具體情況決定的缸夹,如果我們在子類中不需要對父類的成員做出改變的話痪寻,就不存在第 3 步。而在這種情況下虽惭,Swift 會自動地對父類的對應 init 方法進行調(diào)用橡类,也就是說,第 2 步的 super.init() 也是可以不用寫的 (但是實際上還是調(diào)用的芽唇,只不過是為了簡便 Swift 幫我們完成了)顾画。這種情況下的初始化方法看起來就很簡單:

class Cat {
    var name: String
    init() {
        name = "cat"
    }
}

class Tiger: Cat {
    let power: Int
    override init() {
        power = 10
        // super.init()
        // name = "tiger"
    }
}

可以省略的條件:

  1. 子類重寫父類的指定構(gòu)造器
  2. 指定構(gòu)造器沒有參數(shù)
  3. 父類只有一個指定構(gòu)造器
  4. 子類中不需要對父類的成員做出改變
構(gòu)造器的自動繼承

如上所述,子類在默認情況下不會繼承父類的構(gòu)造器披摄。但是如果滿足特定條件亲雪,父類構(gòu)造器是可以被自動繼承的。在實踐中疚膊,這意味著對于許多常見場景你不必重寫父類的構(gòu)造器义辕,并且可以在安全的情況下以最小的代價繼承父類的構(gòu)造器。

假設你為子類中引入的所有新屬性都提供了默認值寓盗,以下 2 個規(guī)則適用:

規(guī)則 1

如果子類沒有定義任何指定構(gòu)造器灌砖,它將自動繼承所有父類的指定構(gòu)造器璧函。

規(guī)則 2

如果子類提供了所有父類指定構(gòu)造器的實現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實現(xiàn)——它將自動繼承所有父類的便利構(gòu)造器基显。

即使你在子類中添加了更多的便利構(gòu)造器蘸吓,這兩條規(guī)則仍然適用。

指定構(gòu)造器和便利構(gòu)造器實踐

接下來的例子將在實踐中展示指定構(gòu)造器撩幽、便利構(gòu)造器以及構(gòu)造器的自動繼承库继。這個例子定義了包含三個類FoodRecipeIngredient以及ShoppingListItem的類層次結(jié)構(gòu)窜醉,并將演示它們的構(gòu)造器是如何相互作用的宪萄。

類層次中的基類是Food,它是一個簡單的用來封裝食物名字的類榨惰。Food類引入了一個叫做nameString類型的屬性拜英,并且提供了兩個構(gòu)造器來創(chuàng)建Food實例:

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

下圖中展示了Food的構(gòu)造器鏈:

Food構(gòu)造器鏈
Food構(gòu)造器鏈

類類型沒有默認的逐一成員構(gòu)造器,所以Food類提供了一個接受單一參數(shù)name的指定構(gòu)造器琅催。這個構(gòu)造器可以使用一個特定的名字來創(chuàng)建新的Food實例:

let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon”

Food類中的構(gòu)造器init(name: String)被定義為一個指定構(gòu)造器居凶,因為它能確保Food實例的所有存儲型屬性都被初始化。Food類沒有父類藤抡,所以init(name: String)構(gòu)造器不需要調(diào)用super.init()來完成構(gòu)造過程侠碧。

Food類同樣提供了一個沒有參數(shù)的便利構(gòu)造器init()。這個init()構(gòu)造器為新食物提供了一個默認的占位名字杰捂,通過橫向代理到指定構(gòu)造器init(name: String)并給參數(shù)name傳值[Unnamed]來實現(xiàn):

let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]

類層級中的第二個類是Food的子類RecipeIngredient舆床。RecipeIngredient類用來表示食譜中的一項原料。它引入了Int類型的屬性quantity(以及從Food繼承過來的name屬性)嫁佳,并且定義了兩個構(gòu)造器來創(chuàng)建RecipeIngredient實例:

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

下圖中展示了RecipeIngredient類的構(gòu)造器鏈:

RecipeIngredient構(gòu)造器
RecipeIngredient構(gòu)造器

RecipeIngredient類擁有一個指定構(gòu)造器init(name: String, quantity: Int)挨队,它可以用來填充RecipeIngredient實例的所有屬性值。這個構(gòu)造器一開始先將傳入的quantity參數(shù)賦值給quantity屬性蒿往,這個屬性也是唯一在RecipeIngredient中新引入的屬性盛垦。隨后,構(gòu)造器向上代理到父類Foodinit(name: String)瓤漏。這個過程滿足兩段式構(gòu)造過程中的安全檢查 1腾夯。

RecipeIngredient還定義了一個便利構(gòu)造器init(name: String),它只通過name來創(chuàng)建RecipeIngredient的實例蔬充。這個便利構(gòu)造器假設任意RecipeIngredient實例的quantity1蝶俱,所以不需要顯式指明數(shù)量即可創(chuàng)建出實例。這個便利構(gòu)造器的定義可以更加方便和快捷地創(chuàng)建實例饥漫,并且避免了創(chuàng)建多個quantity1RecipeIngredient實例時的代碼重復榨呆。這個便利構(gòu)造器只是簡單地橫向代理到類中的指定構(gòu)造器,并為quantity參數(shù)傳遞1庸队。

注意积蜻,RecipeIngredient的便利構(gòu)造器init(name: String)使用了跟Food中指定構(gòu)造器init(name: String)相同的參數(shù)闯割。由于這個便利構(gòu)造器重寫了父類的指定構(gòu)造器init(name: String),因此必須在前面使用override修飾符(參見構(gòu)造器的繼承和重寫)竿拆。

盡管RecipeIngredient將父類的指定構(gòu)造器重寫為了便利構(gòu)造器宙拉,它依然提供了父類的所有指定構(gòu)造器的實現(xiàn)。因此丙笋,RecipeIngredient會自動繼承父類的所有便利構(gòu)造器谢澈。

在這個例子中,RecipeIngredient的父類是Food不见,它有一個便利構(gòu)造器init()澳化。這個便利構(gòu)造器會被RecipeIngredient繼承。這個繼承版本的init()在功能上跟Food提供的版本是一樣的稳吮,只是它會代理到RecipeIngredient版本的init(name: String)而不是Food提供的版本。

所有的這三種構(gòu)造器都可以用來創(chuàng)建新的RecipeIngredient實例:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

類層級中第三個也是最后一個類是RecipeIngredient的子類井濒,叫做ShoppingListItem灶似。這個類構(gòu)建了購物單中出現(xiàn)的某一種食譜原料。

購物單中的每一項總是從未購買狀態(tài)開始的瑞你。為了呈現(xiàn)這一事實酪惭,ShoppingListItem引入了一個布爾類型的屬性purchased,它的默認值是false者甲。ShoppingListItem還添加了一個計算型屬性description春感,它提供了關于ShoppingListItem實例的一些文字描述:

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}

注意
ShoppingListItem沒有定義構(gòu)造器來為purchased提供初始值,因為添加到購物單的物品的初始狀態(tài)總是未購買虏缸。

由于它為自己引入的所有屬性都提供了默認值鲫懒,并且自己沒有定義任何構(gòu)造器,ShoppingListItem將自動繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器刽辙。

下圖展示了這三個類的構(gòu)造器鏈:

三類構(gòu)造器圖
三類構(gòu)造器圖

你可以使用全部三個繼承來的構(gòu)造器來創(chuàng)建ShoppingListItem的新實例:

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x orange juice ?
// 1 x bacon ?
// 6 x eggs ?

如上所述窥岩,例子中通過字面量方式創(chuàng)建了一個數(shù)組breakfastList,它包含了三個ShoppingListItem實例宰缤,因此數(shù)組的類型也能被自動推導為[ShoppingListItem]颂翼。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個ShoppingListItem實例的名字從[Unnamed]更改為Orange juice慨灭,并標記為已購買朦乏。打印數(shù)組中每個元素的描述顯示了它們都已按照預期被賦值。

可失敗構(gòu)造器

如果一個類氧骤、結(jié)構(gòu)體或枚舉類型的對象呻疹,在構(gòu)造過程中有可能失敗,則為其定義一個可失敗構(gòu)造器语淘。這里所指的“失敗”是指诲宇,如給構(gòu)造器傳入無效的參數(shù)值际歼,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等姑蓝。

為了妥善處理這種構(gòu)造過程中可能會失敗的情況鹅心。你可以在一個類,結(jié)構(gòu)體或是枚舉類型的定義中纺荧,添加一個或多個可失敗構(gòu)造器旭愧。其語法為在init關鍵字后面添加問號(init?)。

注意
可失敗構(gòu)造器的參數(shù)名和參數(shù)類型宙暇,不能與其它非可失敗構(gòu)造器的參數(shù)名输枯,及其參數(shù)類型相同。

可失敗構(gòu)造器會創(chuàng)建一個類型為自身類型的可選類型的對象占贫。你通過return nil語句來表明可失敗構(gòu)造器在何種情況下應該“失敗”桃熄。

注意
嚴格來說,構(gòu)造器都不支持返回值型奥。因為構(gòu)造器本身的作用瞳收,只是為了確保對象能被正確構(gòu)造。因此你只是用return nil表明可失敗構(gòu)造器構(gòu)造失敗厢汹,而不要用關鍵字return來表明構(gòu)造成功螟深。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}
構(gòu)造失敗的傳遞

類,結(jié)構(gòu)體烫葬,枚舉的可失敗構(gòu)造器可以橫向代理到類型中的其他可失敗構(gòu)造器界弧。類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器搭综。

無論是向上代理還是橫向代理垢箕,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個構(gòu)造過程將立即終止设凹,接下來的任何構(gòu)造代碼不會再被執(zhí)行舰讹。

注意
可失敗構(gòu)造器也可以代理到其它的非可失敗構(gòu)造器。通過這種方式闪朱,你可以增加一個可能的失敗狀態(tài)到現(xiàn)有的構(gòu)造過程中月匣。

下面這個例子,定義了一個名為CartItemProduct類的子類奋姿。這個類建立了一個在線購物車中的物品的模型锄开,它有一個名為quantity的常量存儲型屬性,并確保該屬性的值至少為1

class Product {
    let name: String
    init?(name: String) {   
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}

CartItem 可失敗構(gòu)造器首先驗證接收的 quantity 值是否大于等于 1 称诗。倘若 quantity 值無效萍悴,則立即終止整個構(gòu)造過程,返回失敗結(jié)果,且不再執(zhí)行余下代碼癣诱。同樣地计维,Product 的可失敗構(gòu)造器首先檢查 name 值,假如 name 值為空字符串撕予,則構(gòu)造器立即執(zhí)行失敗鲫惶。

如果你通過傳入一個非空字符串 name 以及一個值大于等于 1 的 quantity 來創(chuàng)建一個 CartItem 實例,那么構(gòu)造方法能夠成功被執(zhí)行:

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2”

倘若你以一個值為 0 的 quantity 來創(chuàng)建一個 CartItem 實例实抡,那么將導致 CartItem 構(gòu)造器失斍纺浮:

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// 打印 "Unable to initialize zero shirts”

同樣地,如果你嘗試傳入一個值為空字符串的 name來創(chuàng)建一個 CartItem 實例吆寨,那么將導致父類 Product 的構(gòu)造過程失斏吞省:

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// 打印 "Unable to initialize one unnamed product”
重寫一個可失敗構(gòu)造器

如同其它的構(gòu)造器,你可以在子類中重寫父類的可失敗構(gòu)造器啄清×或者你也可以用子類的非可失敗構(gòu)造器重寫一個父類的可失敗構(gòu)造器。這使你可以定義一個不會構(gòu)造失敗的子類辣卒,即使父類的構(gòu)造器允許構(gòu)造失敗缩擂。

注意,當你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時添寺,向上代理到父類的可失敗構(gòu)造器的唯一方式是對父類的可失敗構(gòu)造器的返回值進行強制解包。

注意
你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器懈费,但反過來卻不行计露。

下例定義了一個名為Document的類,name屬性的值必須為一個非空字符串或nil委粉,但不能是一個空字符串:

class Document {
    var name: String?
    // 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為 nil 的 document 實例
    init() {}
    // 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為非空字符串的 document 實例
    init?(name: String) {
        self.name = name
        if name.isEmpty { return nil }
    }
}

下面這個例子泥兰,定義了一個Document類的子類AutomaticallyNamedDocument庆聘。這個子類重寫了父類的兩個指定構(gòu)造器,確保了無論是使用init()構(gòu)造器该押,還是使用init(name:)構(gòu)造器并為參數(shù)傳遞空字符串,生成的實例中的name屬性總有初始"[Untitled]"

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

AutomaticallyNamedDocument用一個非可失敗構(gòu)造器init(name:)重寫了父類的可失敗構(gòu)造器init?(name:)阵谚。因為子類用另一種方式處理了空字符串的情況蚕礼,所以不再需要一個可失敗構(gòu)造器,因此子類用一個非可失敗構(gòu)造器代替了父類的可失敗構(gòu)造器梢什。

你可以在子類的非可失敗構(gòu)造器中使用強制解包來調(diào)用父類的可失敗構(gòu)造器奠蹬。比如,下面的UntitledDocument子類的name屬性的值總是"[Untitled]"嗡午,它在構(gòu)造過程中使用了父類的可失敗構(gòu)造器init?(name:)

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

在這個例子中囤躁,如果在調(diào)用父類的可失敗構(gòu)造器init?(name:)時傳入的是空字符串,那么強制解包操作會引發(fā)運行時錯誤。不過狸演,因為這里是通過非空的字符串常量來調(diào)用它言蛇,所以并不會發(fā)生運行時錯誤。

必要構(gòu)造器

在類的構(gòu)造器前添加required修飾符表明所有該類的子類都必須實現(xiàn)該構(gòu)造器:

class SomeClass {
    required init() {
        // 構(gòu)造器的實現(xiàn)代碼
    }
}

在子類重寫父類的必要構(gòu)造器時宵距,必須在子類的構(gòu)造器前也添加required修飾符腊尚,表明該構(gòu)造器要求也應用于繼承鏈后面的子類。在重寫父類中必要的指定構(gòu)造器時消玄,不需要添加override修飾符:

class SomeSubclass: SomeClass {
    required init() {
        // 構(gòu)造器的實現(xiàn)代碼
    }
}

注意
如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的要求跟伏,則無須在子類中顯式提供必要構(gòu)造器的實現(xiàn)。

析構(gòu)過程(Deinitialization)

析構(gòu)器只適用于類類型翩瓜,當一個類的實例被釋放之前受扳,析構(gòu)器會被立即調(diào)用。析構(gòu)器用關鍵字deinit來標示兔跌,類似于構(gòu)造器要用init來標示勘高。

析構(gòu)過程原理

Swift 會自動釋放不再需要的實例以釋放資源。當你的實例被釋放時不需要手動地去清理坟桅。但是华望,當使用自己的資源時,你可能需要進行一些額外的清理仅乓。

在類的定義中赖舟,每個類最多只能有一個析構(gòu)器,而且析構(gòu)器不帶任何參數(shù)夸楣,如下所示:

deinit {
    // 執(zhí)行析構(gòu)過程
}

析構(gòu)器是在實例釋放發(fā)生前被自動調(diào)用宾抓。你不能主動調(diào)用析構(gòu)器。子類繼承了父類的析構(gòu)器豫喧,并且在子類析構(gòu)器實現(xiàn)的最后石洗,父類的析構(gòu)器會被自動調(diào)用。即使子類沒有提供自己的析構(gòu)器紧显,父類的析構(gòu)器也同樣會被調(diào)用讲衫。

因為直到實例的析構(gòu)器被調(diào)用后,實例才會被釋放孵班,所以析構(gòu)器可以訪問實例的所有屬性涉兽,并且可以根據(jù)那些屬性可以修改它的行為)。

class Person {
    var name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var p: Person?
p = Person(name: "dd")
p = nil

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末重父,一起剝皮案震驚了整個濱河市花椭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌房午,老刑警劉巖矿辽,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡袋倔,警方通過查閱死者的電腦和手機雕蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宾娜,“玉大人批狐,你說我怎么就攤上這事∏八” “怎么了嚣艇?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長华弓。 經(jīng)常有香客問我食零,道長,這世上最難降的妖魔是什么寂屏? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任贰谣,我火速辦了婚禮,結(jié)果婚禮上迁霎,老公的妹妹穿的比我還像新娘吱抚。我一直安慰自己,他們只是感情好考廉,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布秘豹。 她就那樣靜靜地躺著,像睡著了一般昌粤。 火紅的嫁衣襯著肌膚如雪憋肖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天婚苹,我揣著相機與錄音,去河邊找鬼鸵膏。 笑死膊升,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谭企。 我是一名探鬼主播廓译,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼债查!你這毒婦竟也來了非区?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤盹廷,失蹤者是張志新(化名)和其女友劉穎征绸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡管怠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年淆衷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渤弛。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡祝拯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出她肯,到底是詐尸還是另有隱情佳头,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布晴氨,位于F島的核電站康嘉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瑞筐。R本人自食惡果不足惜凄鼻,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聚假。 院中可真熱鬧块蚌,春花似錦、人聲如沸膘格。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘪贱。三九已至纱控,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菜秦,已是汗流浹背甜害。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留球昨,地道東北人尔店。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像主慰,于是被迫代替她去往敵國和親嚣州。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容