這里的教程為Swift官方教程中文版。

構(gòu)造過程


1.0 翻譯:lifedim 校對:lifedim

2.0 翻譯+校對:chenmingbiao

2.1 翻譯:ChanneRealank 校對:shanks,2016-1-23

2.2 翻譯:pmst 翻譯+校對:SketchK 2016-05-14
3.0.1,shanks,2016-11-13

3.1 翻譯:qhd 2017-04-18

本頁包含內(nèi)容:

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

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

類的實例也可以通過定義析構(gòu)器在實例釋放之前執(zhí)行特定的清除工作。想了解更多關(guān)于析構(gòu)器的內(nèi)容,請參考析構(gòu)過程

存儲屬性的初始賦值

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

你可以在構(gòu)造器中為存儲型屬性賦初值,也可以在定義屬性時為其設(shè)置默認值。以下小節(jié)將詳細介紹這兩種方法。

注意
當(dāng)你為存儲型屬性設(shè)置默認值或者在構(gòu)造器中為其賦值時,它們的值是被直接設(shè)置的,不會觸發(fā)任何屬性觀察者。

構(gòu)造器

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

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

下面例子中定義了一個用來保存華氏溫度的結(jié)構(gòu)體Fahrenheit,它擁有一個Double類型的存儲型屬性temperature

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"

這個結(jié)構(gòu)體定義了一個不帶參數(shù)的構(gòu)造器init,并在里面將存儲型屬性temperature的值初始化為32.0(華氏溫度下水的冰點)。

默認屬性值

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

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

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

struct Fahrenheit {
    var temperature = 32.0
}

自定義構(gòu)造過程

你可以通過輸入?yún)?shù)和可選類型的屬性來自定義構(gòu)造過程,也可以在構(gòu)造過程中修改常量屬性。這些都將在后面章節(jié)中提到。

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

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

下面例子中定義了一個包含攝氏度溫度的結(jié)構(gòu)體Celsius。它定義了兩個不同的構(gòu)造器:init(fromFahrenheit:)init(fromKelvin:),二者分別通過接受不同溫標下的溫度值來創(chuàng)建新的實例:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

第一個構(gòu)造器擁有一個構(gòu)造參數(shù),其外部名字為fromFahrenheit,內(nèi)部名字為fahrenheit;第二個構(gòu)造器也擁有一個構(gòu)造參數(shù),其外部名字為fromKelvin,內(nèi)部名字為kelvin。這兩個構(gòu)造器都將唯一的參數(shù)值轉(zhuǎn)換成攝氏溫度值,并保存在屬性temperatureInCelsius中。

參數(shù)的內(nèi)部名稱和外部名稱

跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也擁有一個在構(gòu)造器內(nèi)部使用的參數(shù)名字和一個在調(diào)用構(gòu)造器時使用的外部參數(shù)名字。

然而,構(gòu)造器并不像函數(shù)和方法那樣在括號前有一個可辨別的名字。因此在調(diào)用構(gòu)造器時,主要通過構(gòu)造器中的參數(shù)名和類型來確定應(yīng)該被調(diào)用的構(gòu)造器。正因為參數(shù)如此重要,如果你在定義構(gòu)造器時沒有提供參數(shù)的外部名字,Swift 會為構(gòu)造器的每個參數(shù)自動生成一個跟內(nèi)部名字相同的外部名。

以下例子中定義了一個結(jié)構(gòu)體Color,它包含了三個常量:red、greenblue。這些屬性可以存儲0.01.0之間的值,用來指示顏色中紅、綠、藍成分的含量。

Color提供了一個構(gòu)造器,其中包含三個Double類型的構(gòu)造參數(shù)。Color也可以提供第二個構(gòu)造器,它只包含名為whiteDouble類型的參數(shù),它被用于給上述三個構(gòu)造參數(shù)賦予同樣的值。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

兩種構(gòu)造器都能用于創(chuàng)建一個新的Color實例,你需要為構(gòu)造器每個外部參數(shù)傳值:

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

注意,如果不通過外部參數(shù)名字傳值,你是沒法調(diào)用這個構(gòu)造器的。只要構(gòu)造器定義了某個外部參數(shù)名,你就必須使用它,忽略它將導(dǎo)致編譯錯誤:

let veryGreen = Color(0.0, 1.0, 0.0)
// 報編譯時錯誤,需要外部名稱

不帶外部名的構(gòu)造器參數(shù)

如果你不希望為構(gòu)造器的某個參數(shù)提供外部名字,你可以使用下劃線(_)來顯式描述它的外部名,以此重寫上面所說的默認行為。

下面是之前Celsius例子的擴展,跟之前相比添加了一個帶有Double類型參數(shù)的構(gòu)造器,其外部名用_代替:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 為 37.0

調(diào)用Celsius(37.0)意圖明確,不需要外部參數(shù)名稱。因此適合使用init(_ celsius: Double)這樣的構(gòu)造器,從而可以通過提供Double類型的參數(shù)值調(diào)用構(gòu)造器,而不需要加上外部名。

可選屬性類型

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

下面例子中定義了類SurveyQuestion,它包含一個可選字符串屬性response

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 打印 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

調(diào)查問題的答案在回答前是無法確定的,因此我們將屬性response聲明為String?類型,或者說是可選字符串類型。當(dāng)SurveyQuestion實例化時,它將自動賦值為nil,表明此字符串暫時還沒有值。

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

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

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

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

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?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

默認構(gòu)造器

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

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

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

由于ShoppingListItem類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設(shè)置默認值的默認構(gòu)造器(盡管代碼中沒有顯式為name屬性設(shè)置默認值,但由于name是可選字符串類型,它將默認設(shè)置為nil)。上面例子中使用默認構(gòu)造器創(chuàng)造了一個ShoppingListItem類的實例(使用ShoppingListItem()形式的構(gòu)造器語法),并將其賦值給變量item

結(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ù)名進行傳值來完成對成員屬性的初始賦值。

下面例子中定義了一個結(jié)構(gòu)體Size,它包含兩個屬性widthheight。Swift 可以根據(jù)這兩個屬性的初始賦值0.0自動推導(dǎo)出它們的類型為Double。

結(jié)構(gòu)體Size自動獲得了一個逐一成員構(gòu)造器init(width:height:)。你可以用它來為Size創(chuàng)建新的實例:

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)造器間的代碼重復(fù)。

構(gòu)造器代理的實現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過程相對簡單,因為它們只能代理給自己的其它構(gòu)造器。類則不同,它可以繼承自其它類(請參考繼承),這意味著類有責(zé)任保證其所有繼承的存儲型屬性在構(gòu)造時也能正確的初始化。這些責(zé)任將在后續(xù)章節(jié)類的繼承和構(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)造器)。這種限制可以防止你為值類型增加了一個額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯誤的使用自動生成的構(gòu)造器

注意
假如你希望默認構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實例,可以將自定義的構(gòu)造器寫到擴展(extension)中,而不是寫在值類型的原始定義中。想查看更多內(nèi)容,請查看擴展章節(jié)。

下面例子將定義一個結(jié)構(gòu)體Rect,用來代表幾何矩形。這個例子需要兩個輔助的結(jié)構(gòu)體SizePoint,它們各自為其所有的屬性提供了初始值0.0。

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

你可以通過以下三種方式為Rect創(chuàng)建實例——使用被初始化為默認值的originsize屬性來初始化;提供指定的originsize實例來初始化;提供指定的centersize來初始化。在下面Rect結(jié)構(gòu)體定義中,我們?yōu)檫@三種方式提供了三個自定義的構(gòu)造器:

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)
    }
}

第一個Rect構(gòu)造器init(),在功能上跟沒有自定義構(gòu)造器時自動獲得的默認構(gòu)造器是一樣的。這個構(gòu)造器是一個空函數(shù),使用一對大括號{}來表示,它沒有執(zhí)行任何構(gòu)造過程。調(diào)用這個構(gòu)造器將返回一個Rect實例,它的originsize屬性都使用定義時的默認值Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

let basicRect = Rect()
// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)

第二個Rect構(gòu)造器init(origin:size:),在功能上跟結(jié)構(gòu)體在沒有自定義構(gòu)造器時獲得的逐一成員構(gòu)造器是一樣的。這個構(gòu)造器只是簡單地將originsize的參數(shù)值賦給對應(yīng)的存儲型屬性:

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))
// originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)

第三個Rect構(gòu)造器init(center:size:)稍微復(fù)雜一點。它先通過centersize的值計算出origin的坐標,然后再調(diào)用(或者說代理給)init(origin:size:)構(gòu)造器來將新的originsize值賦值到對應(yīng)的屬性中:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)

構(gòu)造器init(center:size:)可以直接將originsize的新值賦值到對應(yīng)的屬性中。然而,利用恰好提供了相關(guān)功能的現(xiàn)有構(gòu)造器會更為方便,構(gòu)造器init(center:size:)的意圖也會更加清晰。

注意
如果你想用另外一種不需要自己定義init()init(origin:size:)的方式來實現(xiàn)這個例子,請參考擴展。

類的繼承和構(gòu)造過程

類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值。

Swift 為類類型提供了兩種構(gòu)造器來確保實例中所有存儲型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。

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

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

每一個類都必須擁有至少一個指定構(gòu)造器。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個條件。具體內(nèi)容請參考后續(xù)章節(jié)構(gòu)造器的自動繼承。

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

你應(yīng)當(dā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關(guān)鍵字之前放置convenience關(guān)鍵字,并使用空格將它們倆分開:

convenience init(parameters) {
    statements
}

類的構(gòu)造器代理規(guī)則

為了簡化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,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)造器必須最終導(dǎo)致一個指定構(gòu)造器被調(diào)用。

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

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

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

構(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)。

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

復(fù)雜構(gòu)造器代理圖

兩段式構(gòu)造過程

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

兩段式構(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)造流程則更加靈活,它允許你設(shè)置定制的初始值,并自如應(yīng)對某些屬性不能以0nil作為合法默認值的情況。

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

安全檢查 1

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

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

安全檢查 2

指定構(gòu)造器必須先向上代理調(diào)用父類構(gòu)造器,然后再為繼承的屬性設(shè)置新值。如果沒這么做,指定構(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)造器鏈的最頂部。
  • 當(dāng)?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)造過程從對子類中一個便利構(gòu)造器的調(diào)用開始。這個便利構(gòu)造器此時沒法修改任何屬性,它把構(gòu)造任務(wù)代理給同一類中的指定構(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)造器現(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)造器僅會在安全和適當(dāng)?shù)那闆r下被繼承。具體內(nèi)容請參考后續(xù)章節(jié)構(gòu)造器的自動繼承

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

當(dāng)你在編寫一個和父類中指定構(gòu)造器相匹配的子類構(gòu)造器時,你實際上是在重寫父類的這個指定構(gòu)造器。因此,你必須在定義子類構(gòu)造器時帶上override修飾符。即使你重寫的是系統(tǒng)自動提供的默認構(gòu)造器,也需要帶上override修飾符,具體內(nèi)容請參考默認構(gòu)造器。

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

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

相反,如果你編寫了一個和父類便利構(gòu)造器相匹配的子類構(gòu)造器,由于子類不能直接調(diào)用父類的便利構(gòu)造器(每個規(guī)則都在上文類的構(gòu)造器代理規(guī)則有所描述),因此,嚴格意義上來講,你的子類并未對一個父類構(gòu)造器提供重寫。最后的結(jié)果就是,你在子類中“重寫”一個父類便利構(gòu)造器時,不需要加override前綴。

在下面的例子中定義了一個叫Vehicle的基類?;愔新暶髁艘粋€存儲型屬性numberOfWheels,它是值為0Int類型的存儲型屬性。numberOfWheels屬性用于創(chuàng)建名為descrpiptionString類型的計算型屬性:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle類只為存儲型屬性提供默認值,而不自定義構(gòu)造器。因此,它會自動獲得一個默認構(gòu)造器,具體內(nèi)容請參考默認構(gòu)造器。自動獲得的默認構(gòu)造器總會是類中的指定構(gòu)造器,它可以用于創(chuàng)建numberOfWheels0Vehicle實例:

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

下面例子中定義了一個Vehicle的子類Bicycle

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

子類Bicycle定義了一個自定義指定構(gòu)造器init()。這個指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以Bicycle中的指定構(gòu)造器需要帶上override修飾符。

Bicycle的構(gòu)造器init()以調(diào)用super.init()方法開始,這個方法的作用是調(diào)用Bicycle的父類Vehicle的默認構(gòu)造器。這樣可以確保Bicycle在修改屬性之前,它所繼承的屬性numberOfWheels能被Vehicle類初始化。在調(diào)用super.init()之后,屬性numberOfWheels的原值被新值2替換。

如果你創(chuàng)建一個Bicycle實例,你可以調(diào)用繼承的description計算型屬性去查看屬性numberOfWheels是否有改變:

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"

注意
子類可以在初始化時修改繼承來的變量屬性,但是不能修改繼承來的常量屬性。

構(gòu)造器的自動繼承

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

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

規(guī)則 1

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

規(guī)則 2

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

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

注意
對于規(guī)則 2,子類可以將父類的指定構(gòu)造器實現(xiàn)為便利構(gòu)造器。

指定構(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)造器鏈

類類型沒有默認的逐一成員構(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的子類RecipeIngredientRecipeIngredient類用來表示食譜中的一項原料。它引入了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)造器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)造器假設(shè)任意RecipeIngredient實例的quantity1,所以不需要顯式指明數(shù)量即可創(chuàng)建出實例。這個便利構(gòu)造器的定義可以更加方便和快捷地創(chuàng)建實例,并且避免了創(chuàng)建多個quantity1RecipeIngredient實例時的代碼重復(fù)。這個便利構(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,它提供了關(guān)于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)造器來創(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ù)組的類型也能被自動推導(dǎo)為[ShoppingListItem]。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個ShoppingListItem實例的名字從[Unnamed]更改為Orange juice,并標記為已購買。打印數(shù)組中每個元素的描述顯示了它們都已按照預(yù)期被賦值。

可失敗構(gòu)造器

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

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

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

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

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

例如,實現(xiàn)針對數(shù)字類型轉(zhuǎn)換的可失敗構(gòu)造器。確保數(shù)字類型之間的轉(zhuǎn)換能保持精確的值,使用這個 init(exactly:) 構(gòu)造器。如果類型轉(zhuǎn)換不能保持值不變,則這個構(gòu)造器構(gòu)造失敗。

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value")
}
// 打印 "12345.0 conversion to Int maintains value"

let valueChanged = Int(exactly: pi)
// valueChanged 是 Int? 類型, 不是 Int 類型

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// 打印 "3.14159 conversion to Int does not maintain value"

下例中,定義了一個名為Animal的結(jié)構(gòu)體,其中有一個名為speciesString類型的常量屬性。同時該結(jié)構(gòu)體還定義了一個接受一個名為speciesString類型參數(shù)的可失敗構(gòu)造器。這個可失敗構(gòu)造器檢查傳入的參數(shù)是否為一個空字符串。如果為空字符串,則構(gòu)造失敗。否則,species屬性被賦值,構(gòu)造成功。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

你可以通過該可失敗構(gòu)造器來構(gòu)建一個Animal的實例,并檢查構(gòu)造過程是否成功:

let someCreature = Animal(species: "Giraffe")
// someCreature 的類型是 Animal? 而不是 Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"

如果你給該可失敗構(gòu)造器傳入一個空字符串作為其參數(shù),則會導(dǎo)致構(gòu)造失敗:

let anonymousCreature = Animal(species: "")
// anonymousCreature 的類型是 Animal?, 而不是 Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"

注意
空字符串(如"",而不是"Giraffe")和一個值為nil的可選類型的字符串是兩個完全不同的概念。上例中的空字符串("")其實是一個有效的,非可選類型的字符串。這里我們之所以讓Animal的可失敗構(gòu)造器構(gòu)造失敗,只是因為對于Animal這個類的species屬性來說,它更適合有一個具體的值,而不是空字符串。

枚舉類型的可失敗構(gòu)造器

你可以通過一個帶一個或多個參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。如果提供的參數(shù)無法匹配任何枚舉成員,則構(gòu)造失敗。

下例中,定義了一個名為TemperatureUnit的枚舉類型。其中包含了三個可能的枚舉成員(Kelvin,Celsius,和Fahrenheit),以及一個根據(jù)Character值找出所對應(yīng)的枚舉成員的可失敗構(gòu)造器:

enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

你可以利用該可失敗構(gòu)造器在三個枚舉成員中獲取一個相匹配的枚舉成員,當(dāng)參數(shù)的值不能與任何枚舉成員相匹配時,則構(gòu)造失?。?/p>

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."

帶原始值的枚舉類型的可失敗構(gòu)造器

帶原始值的枚舉類型會自帶一個可失敗構(gòu)造器init?(rawValue:),該可失敗構(gòu)造器有一個名為rawValue的參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個枚舉成員的原始值匹配,則該構(gòu)造器會構(gòu)造相應(yīng)的枚舉成員,否則構(gòu)造失敗。

因此上面的TemperatureUnit的例子可以重寫為:

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."

構(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 實例,那么將導(dǎo)致 CartItem 構(gòu)造器失?。?/p>

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 實例,那么將導(dǎo)致父類 Product 的構(gòu)造過程失?。?/p>

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)造失敗。

注意,當(dāng)你用子類的非可失敗構(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)造器 init!

通常來說我們通過在init關(guān)鍵字后添加問號的方式(init?)來定義一個可失敗構(gòu)造器,但你也可以通過在init后面添加驚嘆號的方式來定義一個可失敗構(gòu)造器(init!),該可失敗構(gòu)造器將會構(gòu)建一個對應(yīng)類型的隱式解包可選類型的對象。

你可以在init?中代理到init!,反之亦然。你也可以用init?重寫init!,反之亦然。你還可以用init代理到init!,不過,一旦init!構(gòu)造失敗,則會觸發(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)造器要求也應(yīng)用于繼承鏈后面的子類。在重寫父類中必要的指定構(gòu)造器時,不需要添加override修飾符:

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

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

通過閉包或函數(shù)設(shè)置屬性的默認值

如果某個存儲型屬性的默認值需要一些定制或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認值。每當(dāng)某個屬性所在類型的新實例被創(chuàng)建時,對應(yīng)的閉包或函數(shù)會被調(diào)用,而它們的返回值會當(dāng)做默認值賦值給這個屬性。

這種類型的閉包或函數(shù)通常會創(chuàng)建一個跟屬性類型相同的臨時變量,然后修改它的值以滿足預(yù)期的初始狀態(tài),最后返回這個臨時變量,作為屬性的默認值。

下面介紹了如何用閉包為屬性提供默認值:

class SomeClass {
    let someProperty: SomeType = {
        // 在這個閉包中給 someProperty 創(chuàng)建一個默認值
        // someValue 必須和 SomeType 類型相同
        return someValue
    }()
}

注意閉包結(jié)尾的大括號后面接了一對空的小括號。這用來告訴 Swift 立即執(zhí)行此閉包。如果你忽略了這對括號,相當(dāng)于將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。

注意
如果你使用閉包來初始化屬性,請記住在閉包執(zhí)行時,實例的其它部分都還沒有初始化。這意味著你不能在閉包里訪問其它屬性,即使這些屬性有默認值。同樣,你也不能使用隱式的self屬性,或者調(diào)用任何實例方法。

下面例子中定義了一個結(jié)構(gòu)體Checkerboard,它構(gòu)建了西洋跳棋游戲的棋盤:

西洋跳棋棋盤

西洋跳棋游戲在一副黑白格交替的 8 x 8 的棋盤中進行。為了呈現(xiàn)這副游戲棋盤,Checkerboard結(jié)構(gòu)體定義了一個屬性boardColors,它是一個包含100Bool值的數(shù)組。在數(shù)組中,值為true的元素表示一個黑格,值為false的元素表示一個白格。數(shù)組中第一個元素代表棋盤上左上角的格子,最后一個元素代表棋盤上右下角的格子。

boardColor數(shù)組是通過一個閉包來初始化并設(shè)置顏色值的:

struct Checkerboard {
    let boardColors: [Bool] = {
        var temporaryBoard = [Bool]()
        var isBlack = false
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBoard
    }()
    func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

每當(dāng)一個新的Checkerboard實例被創(chuàng)建時,賦值閉包會被執(zhí)行,boardColors的默認值會被計算出來并返回。上面例子中描述的閉包將計算出棋盤中每個格子對應(yīng)的顏色,并將這些值保存到一個臨時數(shù)組temporaryBoard中,最后在構(gòu)建完成時將此數(shù)組作為閉包返回值返回。這個返回的數(shù)組會保存到boardColors中,并可以通過工具函數(shù)squareIsBlackAtRow來查詢:

let board = Checkerboard()
print(board.squareIsBlackAtRow(0, column: 1))
// 打印 "true"
print(board.squareIsBlackAtRow(7, column: 7))
// 打印 "false"
? 繼承 析構(gòu)過程 ?
?