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

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

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

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

類的實(shí)例也可以通過(guò)實(shí)現(xiàn)析構(gòu)器來(lái)執(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ǔ)型屬性分配默認(rèn)值或者在構(gòu)造器中為設(shè)置初始值時(shí),它們的值是被直接設(shè)置的,不會(huì)觸發(fā)任何屬性觀察者。

構(gòu)造器

構(gòu)造器在創(chuàng)建某個(gè)特定類型的新實(shí)例時(shí)被調(diào)用。它的最簡(jiǎn)形式類似于一個(gè)不帶任何形參的實(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è)不帶形參的構(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)造器中賦值要好。兩種方法的最終結(jié)果是一樣的,只不過(guò)使用默認(rèn)值讓屬性的初始化和聲明結(jié)合得更緊密。它能讓你的構(gòu)造器更簡(jiǎn)潔、更清晰,且能通過(guò)默認(rèn)值自動(dòng)推導(dǎo)出屬性的類型;同時(shí),它也能讓你充分利用默認(rèn)構(gòu)造器、構(gòu)造器繼承等特性,后續(xù)章節(jié)將講到。

你可以通過(guò)在屬性聲明時(shí)為 temperature 提供默認(rèn)值來(lái)使用更簡(jiǎn)單的方式定義結(jié)構(gòu)體 Fahrenheit

struct Fahrenheit {
    var temperature = 32.0
}

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

你可以通過(guò)輸入形參和可選屬性類型來(lái)自定義構(gòu)造過(guò)程,也可以在構(gòu)造過(guò)程中分配常量屬性。這些都將在后面章節(jié)中提到。

形參的構(gòu)造過(guò)程

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

下面例子中定義了一個(gè)用來(lái)保存攝氏溫度的結(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í)參標(biāo)簽為 fromFahrenheit,形參命名為 fahrenheit;第二個(gè)構(gòu)造器也擁有一個(gè)構(gòu)造形參,其實(shí)參標(biāo)簽為 fromKelvin,形參命名為 kelvin。這兩個(gè)構(gòu)造器都將單一的實(shí)參轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius 中。

形參命名和實(shí)參標(biāo)簽

跟函數(shù)和方法形參相同,構(gòu)造形參可以同時(shí)使用在構(gòu)造器里使用的形參命名和一個(gè)外部調(diào)用構(gòu)造器時(shí)使用的實(shí)參標(biāo)簽。

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

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

Color 提供了一個(gè)構(gòu)造器,為紅藍(lán)綠提供三個(gè)合適 Double 類型的形參命名。Color 也提供了第二個(gè)構(gòu)造器,它只包含名為 whiteDouble 類型的形參,它為三個(gè)顏色的屬性提供相同的值。

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ò)為每一個(gè)構(gòu)造器形參提供命名值來(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)簽傳值,這個(gè)構(gòu)造器是沒(méi)法調(diào)用的。如果構(gòu)造器定義了某個(gè)實(shí)參標(biāo)簽,就必須使用它,忽略它將導(dǎo)致編譯期錯(cuò)誤:

let veryGreen = Color(0.0, 1.0, 0.0)
// 報(bào)編譯期錯(cuò)誤-需要實(shí)參標(biāo)簽

不帶實(shí)參標(biāo)簽的構(gòu)造器形參

如果你不希望構(gòu)造器的某個(gè)形參使用實(shí)參標(biāo)簽,可以使用下劃線(_)來(lái)代替顯式的實(shí)參標(biāo)簽來(lái)重寫默認(rèn)行為。

下面是之前 形參的構(gòu)造過(guò)程Celsius 例子的擴(kuò)展,多了一個(gè)用已經(jīng)的攝氏表示的 Double 類型值來(lái)創(chuàng)建新的 Celsius 實(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

構(gòu)造器調(diào)用 Celsius(37.0) 意圖明確,不需要實(shí)參標(biāo)簽。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過(guò)提供未命名的 Double 值來(lái)調(diào)用構(gòu)造器。

可選屬性類型

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

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

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

你可以在構(gòu)造過(guò)程中的任意時(shí)間點(diǎn)給常量屬性賦值,只要在構(gòu)造過(guò)程結(jié)束時(shí)它設(shè)置成確定的值。一旦常量屬性被賦值,它將永遠(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)值,又沒(méi)有提供任何自定義的構(gòu)造器,那么 Swift 會(huì)給這些結(jié)構(gòu)體或類提供一個(gè)默認(rèn)構(gòu)造器。這個(gè)默認(rèn)構(gòu)造器將簡(jiǎn)單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為它們默認(rèn)值的實(shí)例。

下面例子中定義了一個(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)值的并創(chuàng)建實(shí)例的默認(rèn)構(gòu)造器(由于 name 屬性是可選 String 類型,它將接收一個(gè)默認(rèn) nil 的默認(rèn)值,盡管代碼中沒(méi)有寫出這個(gè)值)。上面例子中使用默認(rèn)構(gòu)造器創(chuàng)造了一個(gè) ShoppingListItem 類的實(shí)例(使用 ShoppingListItem() 形式的構(gòu)造器語(yǔ)法),并將其賦值給變量 item

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

結(jié)構(gòu)體如果沒(méi)有定義任何自定義構(gòu)造器,它們將自動(dòng)獲得一個(gè)逐一成員構(gòu)造器(memberwise initializer)。不像默認(rèn)構(gòu)造器,即使存儲(chǔ)型屬性沒(méi)有默認(rèn)值,結(jié)構(gòu)體也能會(huì)獲得逐一成員構(gòu)造器。

逐一成員構(gòu)造器是用來(lái)初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法。新實(shí)例的屬性初始值可以通過(guò)名字傳入逐一成員構(gòu)造器中。

下面例子中定義了一個(gè)結(jié)構(gòu)體 Size,它包含兩個(gè)屬性 widthheight。根據(jù)這兩個(gè)屬性默認(rèn)賦值為 0.0 ,它們的類型被推斷出來(lái)為 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)

當(dāng)你調(diào)用一個(gè)逐一成員構(gòu)造器(memberwise initializer)時(shí),可以省略任何一個(gè)有默認(rèn)值的屬性。在上面這個(gè)例子中,Size 結(jié)構(gòu)體的 heightwidth 屬性各有一個(gè)默認(rèn)值。你可以省略兩者或兩者之一,對(duì)于被省略的屬性,構(gòu)造器會(huì)使用默認(rèn)值。舉個(gè)例子:

let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// 打印 "0.0 2.0"

let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// 打印 "0.0 0.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)造器中做了額外的重要設(shè)置,但有人不小心使用自動(dòng)生成的構(gòu)造器而導(dǎo)致錯(cuò)誤的情況。

注意

假如你希望默認(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)造器是函數(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)造器將初始化類中提供的所有屬性,并調(diào)用合適的父類構(gòu)造器讓構(gòu)造過(guò)程沿著父類鏈繼續(xù)往上進(jìn)行。

類傾向于擁有極少的指定構(gòu)造器,普遍的是一個(gè)類只擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器像一個(gè)個(gè)“漏斗”放在構(gòu)造過(guò)程發(fā)生的地方,讓構(gòu)造過(guò)程沿著父類鏈繼續(xù)往上進(jìn)行。

每一個(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)造器,并為部分形參提供默認(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)造器代理

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

規(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í)例才是有效的,才能訪問(wèn)屬性和調(diào)用方法。

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

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

如安全檢查 1 所示,指定構(gòu)造器將確保所有子類的屬性都有值。然后它將調(diào)用父類的指定構(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)一步自定義實(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ì)的子類繼承,而在用來(lái)創(chuàng)建子類時(shí)的新實(shí)例時(shí)沒(méi)有完全或錯(cuò)誤被初始化。

注意

父類的構(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ù)是否被按預(yù)想中被指定。

注意

當(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)值為 Int 類型的 0numberOfWheels 屬性用在一個(gè)描述車輛特征 String 類型為 descrpiption 的計(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)造器。默認(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è)版本的構(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)”

如果子類的構(gòu)造器沒(méi)有在階段 2 過(guò)程中做自定義操作,并且父類有一個(gè)無(wú)參數(shù)的指定構(gòu)造器,你可以在所有子類的存儲(chǔ)屬性賦值之后省略 super.init() 的調(diào)用。

這個(gè)例子定義了另一個(gè) Vehicle 的子類 Hoverboard ,只設(shè)置它的 color 屬性。這個(gè)構(gòu)造器依賴隱式調(diào)用父類的構(gòu)造器來(lái)完成,而不是顯示調(diào)用 super.init()。

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() 在這里被隱式調(diào)用
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

Hoverboard 的實(shí)例用 Vehicle 構(gòu)造器里默認(rèn)的輪子數(shù)量。

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

注意

子類可以在構(gòu)造過(guò)程修改繼承來(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ī)則仍然適用。

注意

子類可以將父類的指定構(gòu)造器實(shí)現(xiàn)為便利構(gòu)造器來(lái)滿足規(guī)則 2。

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

接下來(lái)的例子將在實(shí)踐中展示指定構(gòu)造器、便利構(gòu)造器以及構(gòu)造器的自動(dòng)繼承。這個(gè)例子定義了包含三個(gè)類 Food、RecipeIngredient 以及 ShoppingListItem 的層級(jí)結(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,所以不需要顯式的質(zhì)量即可創(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) 相同的形參。由于這個(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)買。

因?yàn)樗鼮樽约阂氲乃袑傩远继峁┝四J(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)造器

有時(shí),定義一個(gè)構(gòu)造器可失敗的類,結(jié)構(gòu)體或者枚舉是很有用的。這里所指的“失敗” 指的是,如給構(gòu)造器傳入無(wú)效的形參,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。

為了妥善處理這種構(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 類型形參的可失敗構(gòu)造器。這個(gè)可失敗構(gòu)造器檢查傳入的species 值是否為一個(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è)空字符串到形參 species,則會(huì)導(dǎo)致構(gòu)造失?。?/p>

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

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

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

下例中,定義了一個(gè)名為 TemperatureUnit 的枚舉類型。其中包含了三個(gè)可能的枚舉狀態(tài)(Kelvin、CelsiusFahrenheit),以及一個(gè)根據(jù)表示溫度單位的 Character 值找出合適的枚舉成員的可失敗構(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è)枚舉成員中選擇合適的枚舉成員,當(dāng)形參不能和任何枚舉成員相匹配時(shí),則構(gòu)造失敗:

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 形參,選擇找到的相匹配的枚舉成員,找不到則構(gòu)造失敗。

因此上面的 TemperatureUnit 的例子可以用原始值類型的 Character 和進(jìn)階的 init?(rawValue:) 構(gòu)造器重寫為:

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)造器。

無(wú)論是向上代理還是橫向代理,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個(gè)構(gòu)造過(guò)程將立即終止,接下來(lái)的任何構(gòu)造代碼不會(huì)再被執(zhí)行。

注意

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

下面這個(gè)例子,定義了一個(gè)名為 CartItemProduct 類的子類。這個(gè)類建立了一個(gè)在線購(gòu)物車中的物品的模型,它有一個(gè)名為 quantity 的常量存儲(chǔ)型屬性,并確保該屬性的值至少為 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)造器首先驗(yàn)證接收的 quantity 值是否大于等于 1 。倘若 quantity 值無(wú)效,則立即終止整個(gè)構(gòu)造過(guò)程,返回失敗結(jié)果,且不再執(zhí)行余下代碼。同樣地,Product 的可失敗構(gòu)造器首先檢查 name 值,假如 name 值為空字符串,則構(gòu)造器立即執(zhí)行失敗。

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

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

倘若你以一個(gè)值為 0 的 quantity 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(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”

同樣地,如果你嘗試傳入一個(gè)值為空字符串的 name 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(dǎo)致父類 Product 的構(gòu)造過(guò)程失?。?/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è)可失敗構(gòu)造器

如同其它的構(gòu)造器,你可以在子類中重寫父類的可失敗構(gòu)造器?;蛘吣阋部梢杂米宇惖姆强墒?gòu)造器重寫一個(gè)父類的可失敗構(gòu)造器。這使你可以定義一個(gè)不會(huì)構(gòu)造失敗的子類,即使父類的構(gòu)造器允許構(gòu)造失敗。

注意,當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時(shí),向上代理到父類的可失敗構(gòu)造器的唯一方式是對(duì)父類的可失敗構(gòu)造器的返回值進(jìn)行強(qiáng)制解包。

注意

你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器,但反過(guò)來(lái)卻不行。

下例定義了一個(gè)名為 Document 的類。這個(gè)類模擬一個(gè)文檔并可以用 name 屬性來(lái)構(gòu)造,屬性的值必須為一個(gè)非空字符串或 nil,但不能是一個(gè)空字符串:

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

下面這個(gè)例子,定義了一個(gè) Document 類的子類 AutomaticallyNamedDocument。這個(gè)子類重寫了所有父類引入的指定構(gòu)造器。這些重寫確保了無(wú)論是使用 init() 構(gòu)造器,還是使用 init(name:) 構(gòu)造器,在沒(méi)有名字或者形參傳入空字符串時(shí),生成的實(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è)不可失敗構(gòu)造器 init(name:) 重寫了父類的可失敗構(gòu)造器 init?(name:)。因?yàn)樽宇愑昧硪环N方式處理了空字符串的情況,所以不再需要一個(gè)可失敗構(gòu)造器,因此子類用一個(gè)不可失敗構(gòu)造器代替了父類的可失敗構(gòu)造器。

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

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

在這個(gè)例子中,如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:) 時(shí)傳入的是空字符串,那么強(qiáng)制解包操作會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。不過(guò),因?yàn)檫@里是通過(guò)字符串常量來(lái)調(diào)用它,構(gòu)造器不會(huì)失敗,所以并不會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤。

init! 可失敗構(gòu)造器

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

你可以在 init? 中代理到 init!,反之亦然。你也可以用 init? 重寫 init!,反之亦然。你還可以用 init 代理到 init!,不過(guò),一旦 init! 構(gòu)造失敗,則會(huì)觸發(fā)一個(gè)斷言。

必要構(gòu)造器

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

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

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

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

注意

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

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

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

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

下面模板介紹了如何用閉包為屬性提供默認(rèn)值:

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

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

注意

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

下面例子中定義了一個(gè)結(jié)構(gòu)體 Chessboard,它構(gòu)建了西洋跳棋游戲的棋盤,西洋跳棋游戲在一副黑白格交替的 8 x 8 的棋盤中進(jìn)行的:

西洋跳棋棋盤

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

boardColors 數(shù)組是通過(guò)一個(gè)閉包來(lái)初始化并設(shè)置顏色值的:

struct Chessboard {
    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 squareIsBlackAt(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

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

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”
? 繼承 析構(gòu)過(guò)程 ?
?