鍍金池/ 教程/ iOS/ 構(gòu)造過(guò)程
特性(Attributes)
Access Control 權(quán)限控制的黑與白
基本運(yùn)算符(Basic Operators)
基礎(chǔ)部分(The Basics)
閉包(Closures)
擴(kuò)展
泛型參數(shù)(Generic Parameters and Arguments)
訪問(wèn)控制和 protected
語(yǔ)句(Statements)
模式(Patterns)
WWDC 里面的那個(gè)“大炮打氣球”
關(guān)于語(yǔ)言參考(About the Language Reference)
語(yǔ)法總結(jié)(Summary of the Grammar)
嵌套類型
類型(Types)
Swift 初見(jiàn)(A Swift Tour)
泛型
枚舉(Enumerations)
高級(jí)運(yùn)算符
繼承
析構(gòu)過(guò)程
關(guān)于 Swift(About Swift)
訪問(wèn)控制
類和結(jié)構(gòu)體
內(nèi)存安全
Swift 與 C 語(yǔ)言指針友好合作
協(xié)議
屬性(Properties)
可選類型完美解決占位問(wèn)題
錯(cuò)誤處理
字符串和字符(Strings and Characters)
聲明(Declarations)
自動(dòng)引用計(jì)數(shù)
Swift 里的值類型與引用類型
表達(dá)式(Expressions)
Swift 文檔修訂歷史
造個(gè)類型不是夢(mèng)-白話 Swift 類型創(chuàng)建
歡迎使用 Swift
詞法結(jié)構(gòu)(Lexical Structure)
集合類型(Collection Types)
下標(biāo)
方法(Methods)
可選鏈?zhǔn)秸{(diào)用
版本兼容性
類型轉(zhuǎn)換
構(gòu)造過(guò)程
The Swift Programming Language 中文版
函數(shù)(Functions)
Swift 教程
控制流(Control Flow)

構(gòu)造過(guò)程


1.0 翻譯:lifedim 校對(duì):lifedim

2.0 翻譯+校對(duì):chenmingbiao

2.1 翻譯:Channe,Realank 校對(duì):shanks,2016-1-23

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

3.1 翻譯:qhd 2017-04-18

4.0 翻譯:muhlenXi 2017-09-21

4.1 翻譯+校對(duì):mylittleswift

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

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

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

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

存儲(chǔ)屬性的初始賦值

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

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

注意

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

構(gòu)造器

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

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

下面例子中定義了一個(gè)用來(lái)保存華氏溫度的結(jié)構(gòu)體 Fahrenheit,它擁有一個(gè) Double 類型的存儲(chǔ)型屬性 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"

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

默認(rèn)屬性值

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

注意

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

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

struct Fahrenheit {
    var temperature = 32.0
}

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

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

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

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

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

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

參數(shù)名和參數(shù)標(biāo)簽

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

然而,構(gòu)造器并不像函數(shù)和方法那樣在括號(hào)前有一個(gè)可辨別的名字。因此在調(diào)用構(gòu)造器時(shí),主要通過(guò)構(gòu)造器中的參數(shù)名和類型來(lái)確定應(yīng)該被調(diào)用的構(gòu)造器。正因?yàn)閰?shù)如此重要,如果你在定義構(gòu)造器時(shí)沒(méi)有提供參數(shù)標(biāo)簽,Swift 會(huì)為構(gòu)造器的每個(gè)參數(shù)自動(dòng)生成一個(gè)參數(shù)標(biāo)簽。

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

Color 提供了一個(gè)構(gòu)造器,其中包含三個(gè) Double 類型的構(gòu)造參數(shù)。Color 也提供了第二個(gè)構(gòu)造器,它只包含名為 whiteDouble 類型的參數(shù),它被用于給上述三個(gè)構(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)造器都能通過(guò)提供的初始參數(shù)值來(lái)創(chuàng)建一個(gè)新的 Color 實(shí)例:

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

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

let veryGreen = Color(0.0, 1.0, 0.0)
// 報(bào)編譯時(shí)錯(cuò)誤,需要外部名稱

不帶參數(shù)標(biāo)簽的構(gòu)造器參數(shù)

如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供參數(shù)標(biāo)簽,你可以使用下劃線(_)來(lái)顯式描述它的外部名,以此重寫上面所說(shuō)的默認(rèn)行為。

下面是之前 Celsius 例子的擴(kuò)展,跟之前相比添加了一個(gè)帶有 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ù)標(biāo)簽。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過(guò)提供未命名的 Double 值調(diào)用構(gòu)造器,而不需要加上參數(shù)標(biāo)簽。

可選屬性類型

如果你定制的類型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性——無(wú)論是因?yàn)樗鼰o(wú)法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空——你都需要將它定義為 可選類型??蛇x類型的屬性將自動(dòng)初始化為 nil,表示這個(gè)屬性是有意在初始化時(shí)設(shè)置為空的。

下面例子中定義了類 SurveyQuestion,它包含一個(gè)可選字符串屬性 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)查問(wèn)題的答案在回答前是無(wú)法確定的,因此我們將屬性 response 聲明為 String? 類型,或者說(shuō)是 可選字符串類型。當(dāng) SurveyQuestion 實(shí)例化時(shí),它將自動(dòng)賦值為 nil,表明此字符串暫時(shí)還沒(méi)有值。

構(gòu)造過(guò)程中常量屬性的賦值

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

注意

對(duì)于類的實(shí)例來(lái)說(shuō),它的常量屬性只能在定義它的類的構(gòu)造過(guò)程中修改;不能在子類中修改。

你可以修改上面的 SurveyQuestion 示例,用常量屬性替代變量屬性 text,表示問(wèn)題內(nèi)容 textSurveyQuestion 的實(shí)例被創(chuàng)建之后不會(huì)再被修改。盡管 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.)"

默認(rèn)構(gòu)造器

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

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

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

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

結(jié)構(gòu)體的逐一成員構(gòu)造器

除了上面提到的默認(rèn)構(gòu)造器,如果結(jié)構(gòu)體沒(méi)有提供自定義的構(gòu)造器,它們將自動(dòng)獲得一個(gè)逐一成員構(gòu)造器(memberwise initializer),即使結(jié)構(gòu)體的存儲(chǔ)型屬性沒(méi)有默認(rèn)值。

逐一成員構(gòu)造器是用來(lái)初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法。我們?cè)谡{(diào)用逐一成員構(gòu)造器時(shí),通過(guò)與成員屬性名相同的參數(shù)名進(jìn)行傳值來(lái)完成對(duì)成員屬性的初始賦值。

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

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

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

值類型的構(gòu)造器代理

構(gòu)造器可以通過(guò)調(diào)用其它構(gòu)造器來(lái)完成實(shí)例的部分構(gòu)造過(guò)程。這一過(guò)程稱為構(gòu)造器代理,它能避免多個(gè)構(gòu)造器間的代碼重復(fù)。

構(gòu)造器代理的實(shí)現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過(guò)程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o自己的其它構(gòu)造器。類則不同,它可以繼承自其它類(請(qǐng)參考繼承),這意味著類有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化。這些責(zé)任將在后續(xù)章節(jié)類的繼承和構(gòu)造過(guò)程中介紹。

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

請(qǐng)注意,如果你為某個(gè)值類型定義了一個(gè)自定義的構(gòu)造器,你將無(wú)法訪問(wèn)到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體,還將無(wú)法訪問(wèn)逐一成員構(gòu)造器)。這種限制可以防止你為值類型增加了一個(gè)額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯(cuò)誤的使用自動(dòng)生成的構(gòu)造器

注意

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

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

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

你可以通過(guò)以下三種方式為 Rect 創(chuàng)建實(shí)例——使用含有默認(rèn)值的 originsize 屬性來(lái)初始化;提供指定的 originsize 實(shí)例來(lái)初始化;提供指定的 centersize 來(lái)初始化。在下面 Rect 結(jié)構(gòu)體定義中,我們?yōu)檫@三種方式提供了三個(gè)自定義的構(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)
    }
}

第一個(gè) Rect 構(gòu)造器 init(),在功能上跟沒(méi)有自定義構(gòu)造器時(shí)自動(dòng)獲得的默認(rèn)構(gòu)造器是一樣的。這個(gè)構(gòu)造器是一個(gè)空函數(shù),使用一對(duì)大括號(hào) {} 來(lái)表示。調(diào)用這個(gè)構(gòu)造器將返回一個(gè) Rect 實(shí)例,它的 originsize 屬性都使用定義時(shí)的默認(rèn)值 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)

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

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)

第三個(gè) Rect 構(gòu)造器 init(center:size:) 稍微復(fù)雜一點(diǎn)。它先通過(guò) centersize 的值計(jì)算出 origin 的坐標(biāo),然后再調(diào)用(或者說(shuō)代理給)init(origin:size:) 構(gòu)造器來(lái)將新的 originsize 值賦值到對(duì)應(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 的新值賦值到對(duì)應(yīng)的屬性中。然而,構(gòu)造器 init(center:size:) 通過(guò)使用提供了相關(guān)功能的現(xiàn)有構(gòu)造器將會(huì)更加便捷。

注意

如果你想用另外一種不需要自己定義 init()init(origin:size:) 的方式來(lái)實(shí)現(xiàn)這個(gè)例子,請(qǐng)參考擴(kuò)展。

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

類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過(guò)程中設(shè)置初始值。 []() Swift 為類類型提供了兩種構(gòu)造器來(lái)確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。

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

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

類傾向于擁有少量指定構(gòu)造器,普遍的是一個(gè)類擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器在初始化的地方通過(guò)“管道”將初始化過(guò)程持續(xù)到父類鏈。

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

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

你應(yīng)當(dāng)只在必要的時(shí)候?yàn)轭愄峁┍憷麡?gòu)造器,比方說(shuō)某種情況下通過(guò)使用便利構(gòu)造器來(lái)快捷調(diào)用某個(gè)指定構(gòu)造器,能夠節(jié)省更多開(kāi)發(fā)時(shí)間并讓類的構(gòu)造過(guò)程更清晰明了。

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

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

init(parameters) {
    statements
}

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

convenience init(parameters) {
    statements
}

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

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

規(guī)則 1

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

規(guī)則 2

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

規(guī)則 3

    便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。

一個(gè)更方便記憶的方法是:

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

這些規(guī)則可以通過(guò)下面圖例來(lái)說(shuō)明:

構(gòu)造器代理圖

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

子類中包含兩個(gè)指定構(gòu)造器和一個(gè)便利構(gòu)造器。便利構(gòu)造器必須調(diào)用兩個(gè)指定構(gòu)造器中的任意一個(gè),因?yàn)樗荒苷{(diào)用同一個(gè)類里的其他構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3。而兩個(gè)指定構(gòu)造器必須調(diào)用父類中唯一的指定構(gòu)造器,這滿足了規(guī)則 1。

注意

這些規(guī)則不會(huì)影響類的實(shí)例如何創(chuàng)建。任何上圖中展示的構(gòu)造器都可以用來(lái)創(chuàng)建完全初始化的實(shí)例。這些規(guī)則只影響類的構(gòu)造器如何實(shí)現(xiàn)。

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

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

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

Swift 中類的構(gòu)造過(guò)程包含兩個(gè)階段。第一個(gè)階段,類中的每個(gè)存儲(chǔ)型屬性賦一個(gè)初始值。當(dāng)每個(gè)存儲(chǔ)型屬性的初始值被賦值后,第二階段開(kāi)始,它給每個(gè)類一次機(jī)會(huì),在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性。

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

注意

Swift 的兩段式構(gòu)造過(guò)程跟 Objective-C 中的構(gòu)造過(guò)程類似。最主要的區(qū)別在于階段 1,Objective-C 給每一個(gè)屬性賦值 0 或空值(比如說(shuō) 0nil)。Swift 的構(gòu)造流程則更加靈活,它允許你設(shè)置定制的初始值,并自如應(yīng)對(duì)某些屬性不能以 0nil 作為合法默認(rèn)值的情況。

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

安全檢查 1

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

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

安全檢查 2

    指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類構(gòu)造器,如果沒(méi)這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。

安全檢查 3

    便利構(gòu)造器必須為任意屬性(包括同類中定義的)賦新值之前代理調(diào)用同一類中的其它構(gòu)造器,如果沒(méi)這么做,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋。

安全檢查 4

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

類實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有第一階段完成后,該實(shí)例才會(huì)成為有效實(shí)例,才能訪問(wèn)屬性和調(diào)用方法。

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

階段 1
  • 某個(gè)指定構(gòu)造器或便利構(gòu)造器被調(diào)用。
  • 完成新實(shí)例內(nèi)存的分配,但此時(shí)內(nèi)存還沒(méi)有被初始化。
  • 指定構(gòu)造器確保其所在類引入的所有存儲(chǔ)型屬性都已賦初值。存儲(chǔ)型屬性所屬的內(nèi)存完成初始化。
  • 指定構(gòu)造器將調(diào)用父類的構(gòu)造器,完成父類屬性的初始化。
  • 這個(gè)調(diào)用父類構(gòu)造器的過(guò)程沿著構(gòu)造器鏈一直往上執(zhí)行,直到到達(dá)構(gòu)造器鏈的最頂部。
  • 當(dāng)?shù)竭_(dá)了構(gòu)造器鏈最頂部,且已確保所有實(shí)例包含的存儲(chǔ)型屬性都已經(jīng)賦值,這個(gè)實(shí)例的內(nèi)存被認(rèn)為已經(jīng)完全初始化。此時(shí)階段 1 完成。
階段 2
  • 從頂部構(gòu)造器鏈一直往下,每個(gè)構(gòu)造器鏈中類的指定構(gòu)造器都有機(jī)會(huì)進(jìn)一步定制實(shí)例。構(gòu)造器此時(shí)可以訪問(wèn) self、修改它的屬性并調(diào)用實(shí)例方法等等。
  • 最終,任意構(gòu)造器鏈中的便利構(gòu)造器可以有機(jī)會(huì)定制實(shí)例和使用 self。

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

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

在這個(gè)例子中,構(gòu)造過(guò)程從對(duì)子類中一個(gè)便利構(gòu)造器的調(diào)用開(kāi)始。這個(gè)便利構(gòu)造器此時(shí)沒(méi)法修改任何屬性,它把構(gòu)造任務(wù)代理給同一類中的指定構(gòu)造器。

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

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

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

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

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

父類中的指定構(gòu)造器現(xiàn)在有機(jī)會(huì)進(jìn)一步來(lái)定制實(shí)例(盡管這不是必須的)。

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

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

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

跟 Objective-C 中的子類不同,Swift 中的子類默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。Swift 的這種機(jī)制可以防止一個(gè)父類的簡(jiǎn)單構(gòu)造器被一個(gè)更精細(xì)的子類繼承,并被錯(cuò)誤地用來(lái)創(chuàng)建子類的實(shí)例。

注意

父類的構(gòu)造器僅會(huì)在安全和適當(dāng)?shù)那闆r下被繼承。具體內(nèi)容請(qǐng)參考后續(xù)章節(jié)構(gòu)造器的自動(dòng)繼承。

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

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

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

注意

當(dāng)你重寫一個(gè)父類的指定構(gòu)造器時(shí),你總是需要寫 override 修飾符,即使是為了實(shí)現(xiàn)子類的便利構(gòu)造器。

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

在下面的例子中定義了一個(gè)叫 Vehicle 的基類。基類中聲明了一個(gè)存儲(chǔ)型屬性 numberOfWheels,它是默認(rèn)值為 0Int 類型的存儲(chǔ)型屬性。numberOfWheels 屬性用于創(chuàng)建名為 descrpiptionString 類型的計(jì)算型屬性:

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

Vehicle 類只為存儲(chǔ)型屬性提供默認(rèn)值,也沒(méi)有提供自定義構(gòu)造器。因此,它會(huì)自動(dòng)獲得一個(gè)默認(rèn)構(gòu)造器,具體內(nèi)容請(qǐng)參考默認(rèn)構(gòu)造器。自動(dòng)獲得的默認(rèn)構(gòu)造器總是類中的指定構(gòu)造器,它可以用于創(chuàng)建 numberOfWheels0Vehicle 實(shí)例:

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

下面例子中定義了一個(gè) Vehicle 的子類 Bicycle

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

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

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

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

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

注意

子類可以在初始化時(shí)修改繼承來(lái)的變量屬性,但是不能修改繼承來(lái)的常量屬性。

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

如上所述,子類在默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動(dòng)繼承的。事實(shí)上,這意味著對(duì)于許多常見(jiàn)場(chǎng)景你不必重寫父類的構(gòu)造器,并且可以在安全的情況下以最小的代價(jià)繼承父類的構(gòu)造器。

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

規(guī)則 1

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

規(guī)則 2

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

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

注意

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

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

接下來(lái)的例子將在實(shí)踐中展示指定構(gòu)造器、便利構(gòu)造器以及構(gòu)造器的自動(dòng)繼承。這個(gè)例子定義了包含三個(gè)類 FoodRecipeIngredient 以及 ShoppingListItem 的類層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。

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

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

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

Food 構(gòu)造器鏈

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

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

Food 類中的構(gòu)造器 init(name: String) 被定義為一個(gè)指定構(gòu)造器,因?yàn)樗艽_保 Food 實(shí)例的所有存儲(chǔ)型屬性都被初始化。Food 類沒(méi)有父類,所以 init(name: String) 構(gòu)造器不需要調(diào)用 super.init() 來(lái)完成構(gòu)造過(guò)程。

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

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

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

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è)指定構(gòu)造器 init(name: String, quantity: Int),它可以用來(lái)填充 RecipeIngredient 實(shí)例的所有屬性值。這個(gè)構(gòu)造器一開(kāi)始先將傳入的 quantity 參數(shù)賦值給 quantity 屬性,這個(gè)屬性也是唯一在 RecipeIngredient 中新引入的屬性。隨后,構(gòu)造器向上代理到父類 Foodinit(name: String)。這個(gè)過(guò)程滿足兩段式構(gòu)造過(guò)程中的安全檢查 1。

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

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

盡管 RecipeIngredient 將父類的指定構(gòu)造器重寫為了便利構(gòu)造器,但是它依然提供了父類的所有指定構(gòu)造器的實(shí)現(xiàn)。因此,RecipeIngredient 會(huì)自動(dòng)繼承父類的所有便利構(gòu)造器。

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

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

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

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

購(gòu)物單中的每一項(xiàng)總是從未購(gòu)買狀態(tài)開(kāi)始的。為了呈現(xiàn)這一事實(shí),ShoppingListItem 引入了一個(gè) Boolean(布爾類型) 的屬性 purchased,它的默認(rèn)值是 false。ShoppingListItem 還添加了一個(gè)計(jì)算型屬性 description,它提供了關(guān)于 ShoppingListItem 實(shí)例的一些文字描述:

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

注意

ShoppingListItem 沒(méi)有定義構(gòu)造器來(lái)為 purchased 提供初始值,因?yàn)樘砑拥劫?gòu)物單的物品的初始狀態(tài)總是未購(gòu)買。

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

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

三類構(gòu)造器圖

你可以使用三個(gè)繼承來(lái)的構(gòu)造器來(lái)創(chuàng)建 ShoppingListItem 的新實(shí)例:

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 ?

如上所述,例子中通過(guò)字面量方式創(chuàng)建了一個(gè)數(shù)組 breakfastList,它包含了三個(gè) ShoppingListItem 實(shí)例,因此數(shù)組的類型也能被自動(dòng)推導(dǎo)為 [ShoppingListItem]。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個(gè) ShoppingListItem 實(shí)例的名字從 [Unnamed] 更改為 Orange juice,并標(biāo)記狀態(tài)為已購(gòu)買。打印數(shù)組中每個(gè)元素的描述顯示了它們都已按照預(yù)期被賦值。

可失敗構(gòu)造器

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

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

注意

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

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

注意

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

例如,實(shí)現(xiàn)針對(duì)數(shù)字類型轉(zhuǎn)換的可失敗構(gòu)造器。確保數(shù)字類型之間的轉(zhuǎn)換能保持精確的值,使用這個(gè) init(exactly:) 構(gòu)造器。如果類型轉(zhuǎn)換不能保持值不變,則這個(gè)構(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 of \(valueMaintained)")
}
// 打印 "12345.0 conversion to Int maintains value of 12345"

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"

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

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

你可以通過(guò)該可失敗構(gòu)造器來(lái)嘗試構(gòu)建一個(gè) Animal 的實(shí)例,并檢查構(gòu)造過(guò)程是否成功:

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)造器傳入一個(gè)空字符串作為其參數(shù),則會(huì)導(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" )和一個(gè)值為 nil 的可選類型的字符串是兩個(gè)完全不同的概念。上例中的空字符串("")其實(shí)是一個(gè)有效的,非可選類型的字符串。這里我們之所以讓 Animal 的可失敗構(gòu)造器構(gòu)造失敗,只是因?yàn)閷?duì)于 Animal 這個(gè)類的 species 屬性來(lái)說(shuō),它更適合有一個(gè)具體的值,而不是空字符串。

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

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

下例中,定義了一個(gè)名為 TemperatureUnit 的枚舉類型。其中包含了三個(gè)可能的枚舉成員(Kelvin、CelsiusFahrenheit),以及一個(gè)根據(jù) Character 值找出所對(duì)應(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)造器在三個(gè)枚舉成員中獲取一個(gè)相匹配的枚舉成員,當(dāng)參數(shù)的值不能與任何枚舉成員相匹配時(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)造器

帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:),該可失敗構(gòu)造器有一個(gè)名為 rawValue 的參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個(gè)枚舉成員的原始值匹配,則該構(gòu)造器會(huì)構(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)造器。