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ò)程是使用類(lèi)、結(jié)構(gòu)體或枚舉類(lèi)型的實(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)建特定類(lèi)型新實(shí)例的特殊方法。與 Objective-C 中的構(gòu)造器不同,Swift 的構(gòu)造器無(wú)需返回值,它們的主要任務(wù)是保證新實(shí)例在第一次使用前完成正確的初始化。
類(lèi)的實(shí)例也可以通過(guò)定義析構(gòu)器在實(shí)例釋放之前執(zhí)行特定的清除工作。想了解更多關(guān)于析構(gòu)器的內(nèi)容,請(qǐng)參考析構(gòu)過(guò)程。
類(lèi)和結(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)造器在創(chuàng)建某個(gè)特定類(lèi)型的新實(shí)例時(shí)被調(diào)用。它的最簡(jiǎn)形式類(lèi)似于一個(gè)不帶任何參數(shù)的實(shí)例方法,以關(guān)鍵字 init
命名:
init() {
// 在此處執(zhí)行構(gòu)造過(guò)程
}
下面例子中定義了一個(gè)用來(lái)保存華氏溫度的結(jié)構(gòu)體 Fahrenheit
,它擁有一個(gè) Double
類(lèi)型的存儲(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))。
如前所述,你可以在構(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)出屬性的類(lèi)型;同時(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
}
你可以通過(guò)輸入?yún)?shù)和可選類(lèi)型的屬性來(lái)自定義構(gòu)造過(guò)程,也可以在構(gòu)造過(guò)程中給常量屬性賦初值。這些都將在后面章節(jié)中提到。
自定義構(gòu)造過(guò)程時(shí),可以在定義中提供構(gòu)造參數(shù),指定參數(shù)值的類(lèi)型和名字。構(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ù)相同,構(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)型來(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
、green
和 blue
。這些屬性可以存儲(chǔ) 0.0
到 1.0
之間的值,用來(lái)指示顏色中紅、綠、藍(lán)成分的含量。
Color
提供了一個(gè)構(gòu)造器,其中包含三個(gè) Double
類(lèi)型的構(gòu)造參數(shù)。Color
也提供了第二個(gè)構(gòu)造器,它只包含名為 white
的 Double
類(lèi)型的參數(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ò)誤,需要外部名稱(chēng)
如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供參數(shù)標(biāo)簽,你可以使用下劃線(xiàn)(_
)來(lái)顯式描述它的外部名,以此重寫(xiě)上面所說(shuō)的默認(rèn)行為。
下面是之前 Celsius
例子的擴(kuò)展,跟之前相比添加了一個(gè)帶有 Double
類(lèi)型參數(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)簽。
如果你定制的類(lèi)型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性——無(wú)論是因?yàn)樗鼰o(wú)法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空——你都需要將它定義為 可選類(lèi)型
??蛇x類(lèi)型的屬性將自動(dòng)初始化為 nil
,表示這個(gè)屬性是有意在初始化時(shí)設(shè)置為空的。
下面例子中定義了類(lèi) 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?
類(lèi)型,或者說(shuō)是 可選字符串類(lèi)型
。當(dāng) SurveyQuestion
實(shí)例化時(shí),它將自動(dòng)賦值為 nil
,表明此字符串暫時(shí)還沒(méi)有值。
你可以在構(gòu)造過(guò)程中的任意時(shí)間點(diǎn)給常量屬性指定一個(gè)值,只要在構(gòu)造過(guò)程結(jié)束時(shí)是一個(gè)確定的值。一旦常量屬性被賦值,它將永遠(yuǎn)不可更改。
注意
對(duì)于類(lèi)的實(shí)例來(lái)說(shuō),它的常量屬性只能在定義它的類(lèi)的構(gòu)造過(guò)程中修改;不能在子類(lèi)中修改。
你可以修改上面的 SurveyQuestion
示例,用常量屬性替代變量屬性 text
,表示問(wèn)題內(nèi)容 text
在 SurveyQuestion
的實(shí)例被創(chuàng)建之后不會(huì)再被修改。盡管 text
屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽陬?lè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.)"
如果結(jié)構(gòu)體或類(lèi)的所有屬性都有默認(rèn)值,同時(shí)沒(méi)有自定義的構(gòu)造器,那么 Swift 會(huì)給這些結(jié)構(gòu)體或類(lèi)提供一個(gè)默認(rèn)構(gòu)造器(default initializers)。這個(gè)默認(rèn)構(gòu)造器將簡(jiǎn)單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為默認(rèn)值的實(shí)例。
下面例子中創(chuàng)建了一個(gè)類(lèi) ShoppingListItem
,它封裝了購(gòu)物清單中的某一物品的屬性:名字(name
)、數(shù)量(quantity
)和購(gòu)買(mǎi)狀態(tài) purchase state
:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于 ShoppingListItem
類(lèi)中的所有屬性都有默認(rèn)值,且它是沒(méi)有父類(lèi)的基類(lèi),它將自動(dòng)獲得一個(gè)可以為所有屬性設(shè)置默認(rèn)值的默認(rèn)構(gòu)造器(盡管代碼中沒(méi)有顯式為 name
屬性設(shè)置默認(rèn)值,但由于 name
是可選字符串類(lèi)型,它將默認(rèn)設(shè)置為 nil
)。上面例子中使用默認(rèn)構(gòu)造器創(chuàng)造了一個(gè) ShoppingListItem
類(lèi)的實(shí)例(使用 ShoppingListItem()
形式的構(gòu)造器語(yǔ)法),并將其賦值給變量 item
。
除了上面提到的默認(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è)屬性 width
和 height
。Swift 可以根據(jù)這兩個(gè)屬性的初始賦值 0.0
自動(dòng)推導(dǎo)出它們的類(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)
構(gòu)造器可以通過(guò)調(diào)用其它構(gòu)造器來(lái)完成實(shí)例的部分構(gòu)造過(guò)程。這一過(guò)程稱(chēng)為構(gòu)造器代理,它能避免多個(gè)構(gòu)造器間的代碼重復(fù)。
構(gòu)造器代理的實(shí)現(xiàn)規(guī)則和形式在值類(lèi)型和類(lèi)類(lèi)型中有所不同。值類(lèi)型(結(jié)構(gòu)體和枚舉類(lèi)型)不支持繼承,所以構(gòu)造器代理的過(guò)程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o自己的其它構(gòu)造器。類(lèi)則不同,它可以繼承自其它類(lèi)(請(qǐng)參考繼承),這意味著類(lèi)有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化。這些責(zé)任將在后續(xù)章節(jié)類(lèi)的繼承和構(gòu)造過(guò)程中介紹。
對(duì)于值類(lèi)型,你可以使用 self.init
在自定義的構(gòu)造器中引用相同類(lèi)型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用 self.init
。
請(qǐng)注意,如果你為某個(gè)值類(lèi)型定義了一個(gè)自定義的構(gòu)造器,你將無(wú)法訪問(wèn)到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體,還將無(wú)法訪問(wèn)逐一成員構(gòu)造器)。這種限制可以防止你為值類(lèi)型增加了一個(gè)額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯(cuò)誤的使用自動(dòng)生成的構(gòu)造器
注意
假如你希望默認(rèn)構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來(lái)創(chuàng)建實(shí)例,可以將自定義的構(gòu)造器寫(xiě)到擴(kuò)展(
extension
)中,而不是寫(xiě)在值類(lèi)型的原始定義中。想查看更多內(nèi)容,請(qǐng)查看擴(kuò)展章節(jié)。
下面例子將定義一個(gè)結(jié)構(gòu)體 Rect
,用來(lái)代表幾何矩形。這個(gè)例子需要兩個(gè)輔助的結(jié)構(gòu)體 Size
和 Point
,它們各自為其所有的屬性提供了默認(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)值的 origin
和 size
屬性來(lái)初始化;提供指定的 origin
和 size
實(shí)例來(lái)初始化;提供指定的 center
和 size
來(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í)例,它的 origin
和 size
屬性都使用定義時(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)單地將 origin
和 size
的參數(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ò) center
和 size
的值計(jì)算出 origin
的坐標(biāo),然后再調(diào)用(或者說(shuō)代理給)init(origin:size:)
構(gòu)造器來(lái)將新的 origin
和 size
值賦值到對(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:)
可以直接將 origin
和 size
的新值賦值到對(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ò)展。
類(lèi)里面的所有存儲(chǔ)型屬性——包括所有繼承自父類(lèi)的屬性——都必須在構(gòu)造過(guò)程中設(shè)置初始值。 []() Swift 為類(lèi)類(lèi)型提供了兩種構(gòu)造器來(lái)確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。
指定構(gòu)造器是類(lèi)中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類(lèi)中提供的所有屬性,并根據(jù)父類(lèi)鏈往上調(diào)用父類(lèi)合適的構(gòu)造器來(lái)實(shí)現(xiàn)父類(lèi)的初始化。
類(lèi)傾向于擁有少量指定構(gòu)造器,普遍的是一個(gè)類(lèi)擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器在初始化的地方通過(guò)“管道”將初始化過(guò)程持續(xù)到父類(lèi)鏈。
每一個(gè)類(lèi)都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類(lèi)通過(guò)繼承了父類(lèi)中的指定構(gòu)造器而滿(mǎn)足了這個(gè)條件。具體內(nèi)容請(qǐng)參考后續(xù)章節(jié)構(gòu)造器的自動(dòng)繼承。
便利構(gòu)造器是類(lèi)中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來(lái)調(diào)用同一個(gè)類(lèi)中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。你也可以定義便利構(gòu)造器來(lái)創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。
你應(yīng)當(dāng)只在必要的時(shí)候?yàn)轭?lèi)提供便利構(gòu)造器,比方說(shuō)某種情況下通過(guò)使用便利構(gòu)造器來(lái)快捷調(diào)用某個(gè)指定構(gòu)造器,能夠節(jié)省更多開(kāi)發(fā)時(shí)間并讓類(lèi)的構(gòu)造過(guò)程更清晰明了。
類(lèi)的指定構(gòu)造器的寫(xiě)法跟值類(lèi)型簡(jiǎn)單構(gòu)造器一樣:
init(parameters) {
statements
}
便利構(gòu)造器也采用相同樣式的寫(xiě)法,但需要在 init
關(guān)鍵字之前放置 convenience
關(guān)鍵字,并使用空格將它們倆分開(kāi):
convenience init(parameters) {
statements
}
為了簡(jiǎn)化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,Swift 采用以下三條規(guī)則來(lái)限制構(gòu)造器之間的代理調(diào)用:
指定構(gòu)造器必須調(diào)用其直接父類(lèi)的的指定構(gòu)造器。
便利構(gòu)造器必須調(diào)用同類(lèi)中定義的其它構(gòu)造器。
便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。
一個(gè)更方便記憶的方法是:
這些規(guī)則可以通過(guò)下面圖例來(lái)說(shuō)明:
如圖所示,父類(lèi)中包含一個(gè)指定構(gòu)造器和兩個(gè)便利構(gòu)造器。其中一個(gè)便利構(gòu)造器調(diào)用了另外一個(gè)便利構(gòu)造器,而后者又調(diào)用了唯一的指定構(gòu)造器。這滿(mǎn)足了上面提到的規(guī)則 2 和 3。這個(gè)父類(lèi)沒(méi)有自己的父類(lèi),所以規(guī)則 1 沒(méi)有用到。
子類(lèi)中包含兩個(gè)指定構(gòu)造器和一個(gè)便利構(gòu)造器。便利構(gòu)造器必須調(diào)用兩個(gè)指定構(gòu)造器中的任意一個(gè),因?yàn)樗荒苷{(diào)用同一個(gè)類(lèi)里的其他構(gòu)造器。這滿(mǎn)足了上面提到的規(guī)則 2 和 3。而兩個(gè)指定構(gòu)造器必須調(diào)用父類(lèi)中唯一的指定構(gòu)造器,這滿(mǎn)足了規(guī)則 1。
注意
這些規(guī)則不會(huì)影響類(lèi)的實(shí)例如何創(chuàng)建。任何上圖中展示的構(gòu)造器都可以用來(lái)創(chuàng)建完全初始化的實(shí)例。這些規(guī)則只影響類(lèi)的構(gòu)造器如何實(shí)現(xiàn)。
下面圖例中展示了一種涉及四個(gè)類(lèi)的更復(fù)雜的類(lèi)層級(jí)結(jié)構(gòu)。它演示了指定構(gòu)造器是如何在類(lèi)層級(jí)中充當(dāng)“管道”的作用,在類(lèi)的構(gòu)造器鏈上簡(jiǎn)化了類(lèi)之間的相互關(guān)系。
Swift 中類(lèi)的構(gòu)造過(guò)程包含兩個(gè)階段。第一個(gè)階段,類(lèi)中的每個(gè)存儲(chǔ)型屬性賦一個(gè)初始值。當(dāng)每個(gè)存儲(chǔ)型屬性的初始值被賦值后,第二階段開(kāi)始,它給每個(gè)類(lèi)一次機(jī)會(huì),在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性。
兩段式構(gòu)造過(guò)程的使用讓構(gòu)造過(guò)程更安全,同時(shí)在整個(gè)類(lèi)層級(jí)結(jié)構(gòu)中給予了每個(gè)類(lèi)完全的靈活性。兩段式構(gòu)造過(guò)程可以防止屬性值在初始化之前被訪問(wèn),也可以防止屬性被另外一個(gè)構(gòu)造器意外地賦予不同的值。
注意
Swift 的兩段式構(gòu)造過(guò)程跟 Objective-C 中的構(gòu)造過(guò)程類(lèi)似。最主要的區(qū)別在于階段 1,Objective-C 給每一個(gè)屬性賦值
0
或空值(比如說(shuō)0
或nil
)。Swift 的構(gòu)造流程則更加靈活,它允許你設(shè)置定制的初始值,并自如應(yīng)對(duì)某些屬性不能以0
或nil
作為合法默認(rèn)值的情況。
Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過(guò)程不出錯(cuò)地完成:
指定構(gòu)造器必須保證它所在類(lèi)的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類(lèi)中的構(gòu)造器。
如上所述,一個(gè)對(duì)象的內(nèi)存只有在其所有存儲(chǔ)型屬性確定之后才能完全初始化。為了滿(mǎn)足這一規(guī)則,指定構(gòu)造器必須保證它所在類(lèi)的屬性在它往上代理之前先完成初始化。
指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類(lèi)構(gòu)造器,如果沒(méi)這么做,指定構(gòu)造器賦予的新值將被父類(lèi)中的構(gòu)造器所覆蓋。
便利構(gòu)造器必須為任意屬性(包括同類(lèi)中定義的)賦新值之前代理調(diào)用同一類(lèi)中的其它構(gòu)造器,如果沒(méi)這么做,便利構(gòu)造器賦予的新值將被同一類(lèi)中其它指定構(gòu)造器所覆蓋。
構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實(shí)例方法,不能讀取任何實(shí)例屬性的值,不能引用 self
作為一個(gè)值。
類(lèi)實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有第一階段完成后,該實(shí)例才會(huì)成為有效實(shí)例,才能訪問(wèn)屬性和調(diào)用方法。
以下是兩段式構(gòu)造過(guò)程中基于上述安全檢查的構(gòu)造流程展示:
self
、修改它的屬性并調(diào)用實(shí)例方法等等。self
。下圖展示了在假定的子類(lèi)和父類(lèi)之間的構(gòu)造階段 1:
在這個(gè)例子中,構(gòu)造過(guò)程從對(duì)子類(lèi)中一個(gè)便利構(gòu)造器的調(diào)用開(kāi)始。這個(gè)便利構(gòu)造器此時(shí)沒(méi)法修改任何屬性,它把構(gòu)造任務(wù)代理給同一類(lèi)中的指定構(gòu)造器。
如安全檢查 1 所示,指定構(gòu)造器將確保所有子類(lèi)的屬性都有值。然后它將調(diào)用父類(lèi)的指定構(gòu)造器,并沿著構(gòu)造器鏈一直往上完成父類(lèi)的構(gòu)造過(guò)程。
父類(lèi)中的指定構(gòu)造器確保所有父類(lèi)的屬性都有值。由于沒(méi)有更多的父類(lèi)需要初始化,也就無(wú)需繼續(xù)向上代理。
一旦父類(lèi)中所有屬性都有了初始值,實(shí)例的內(nèi)存被認(rèn)為是完全初始化,階段 1 完成。
以下展示了相同構(gòu)造過(guò)程的階段 2:
父類(lèi)中的指定構(gòu)造器現(xiàn)在有機(jī)會(huì)進(jìn)一步來(lái)定制實(shí)例(盡管這不是必須的)。
一旦父類(lèi)中的指定構(gòu)造器完成調(diào)用,子類(lèi)中的指定構(gòu)造器可以執(zhí)行更多的定制操作(這也不是必須的)。
最終,一旦子類(lèi)的指定構(gòu)造器完成調(diào)用,最開(kāi)始被調(diào)用的便利構(gòu)造器可以執(zhí)行更多的定制操作。
跟 Objective-C 中的子類(lèi)不同,Swift 中的子類(lèi)默認(rèn)情況下不會(huì)繼承父類(lèi)的構(gòu)造器。Swift 的這種機(jī)制可以防止一個(gè)父類(lèi)的簡(jiǎn)單構(gòu)造器被一個(gè)更精細(xì)的子類(lèi)繼承,并被錯(cuò)誤地用來(lái)創(chuàng)建子類(lèi)的實(shí)例。
注意
父類(lèi)的構(gòu)造器僅會(huì)在安全和適當(dāng)?shù)那闆r下被繼承。具體內(nèi)容請(qǐng)參考后續(xù)章節(jié)構(gòu)造器的自動(dòng)繼承。
假如你希望自定義的子類(lèi)中能提供一個(gè)或多個(gè)跟父類(lèi)相同的構(gòu)造器,你可以在子類(lèi)中提供這些構(gòu)造器的自定義實(shí)現(xiàn)。
當(dāng)你在編寫(xiě)一個(gè)和父類(lèi)中指定構(gòu)造器相匹配的子類(lèi)構(gòu)造器時(shí),你實(shí)際上是在重寫(xiě)父類(lèi)的這個(gè)指定構(gòu)造器。因此,你必須在定義子類(lèi)構(gòu)造器時(shí)帶上 override
修飾符。即使你重寫(xiě)的是系統(tǒng)自動(dòng)提供的默認(rèn)構(gòu)造器,也需要帶上 override
修飾符,具體內(nèi)容請(qǐng)參考默認(rèn)構(gòu)造器。
正如重寫(xiě)屬性,方法或者是下標(biāo),override
修飾符會(huì)讓編譯器去檢查父類(lèi)中是否有相匹配的指定構(gòu)造器,并驗(yàn)證構(gòu)造器參數(shù)是否正確。
注意
當(dāng)你重寫(xiě)一個(gè)父類(lèi)的指定構(gòu)造器時(shí),你總是需要寫(xiě)
override
修飾符,即使是為了實(shí)現(xiàn)子類(lèi)的便利構(gòu)造器。
相反,如果你編寫(xiě)了一個(gè)和父類(lèi)便利構(gòu)造器相匹配的子類(lèi)構(gòu)造器,由于子類(lèi)不能直接調(diào)用父類(lèi)的便利構(gòu)造器(每個(gè)規(guī)則都在上文類(lèi)的構(gòu)造器代理規(guī)則有所描述),因此,嚴(yán)格意義上來(lái)講,你的子類(lèi)并未對(duì)一個(gè)父類(lèi)構(gòu)造器提供重寫(xiě)。最后的結(jié)果就是,你在子類(lèi)中“重寫(xiě)”一個(gè)父類(lèi)便利構(gòu)造器時(shí),不需要加 override
修飾符。
在下面的例子中定義了一個(gè)叫 Vehicle
的基類(lèi)。基類(lèi)中聲明了一個(gè)存儲(chǔ)型屬性 numberOfWheels
,它是默認(rèn)值為 0
的 Int
類(lèi)型的存儲(chǔ)型屬性。numberOfWheels
屬性用于創(chuàng)建名為 descrpiption
的 String
類(lèi)型的計(jì)算型屬性:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle
類(lèi)只為存儲(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)造器總是類(lèi)中的指定構(gòu)造器,它可以用于創(chuàng)建 numberOfWheels
為 0
的 Vehicle
實(shí)例:
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
下面例子中定義了一個(gè) Vehicle
的子類(lèi) Bicycle
:
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
子類(lèi) Bicycle
定義了一個(gè)自定義指定構(gòu)造器 init()
。這個(gè)指定構(gòu)造器和父類(lèi)的指定構(gòu)造器相匹配,所以 Bicycle
中的指定構(gòu)造器需要帶上 override
修飾符。
Bicycle
的構(gòu)造器 init()
以調(diào)用 super.init()
方法開(kāi)始,這個(gè)方法的作用是調(diào)用 Bicycle
的父類(lèi) Vehicle
的默認(rèn)構(gòu)造器。這樣可以確保 Bicycle
在修改屬性之前,它所繼承的屬性 numberOfWheels
能被 Vehicle
類(lèi)初始化。在調(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)"
注意
子類(lèi)可以在初始化時(shí)修改繼承來(lái)的變量屬性,但是不能修改繼承來(lái)的常量屬性。
如上所述,子類(lèi)在默認(rèn)情況下不會(huì)繼承父類(lèi)的構(gòu)造器。但是如果滿(mǎn)足特定條件,父類(lèi)構(gòu)造器是可以被自動(dòng)繼承的。事實(shí)上,這意味著對(duì)于許多常見(jiàn)場(chǎng)景你不必重寫(xiě)父類(lèi)的構(gòu)造器,并且可以在安全的情況下以最小的代價(jià)繼承父類(lèi)的構(gòu)造器。
假設(shè)你為子類(lèi)中引入的所有新屬性都提供了默認(rèn)值,以下 2 個(gè)規(guī)則適用:
如果子類(lèi)沒(méi)有定義任何指定構(gòu)造器,它將自動(dòng)繼承父類(lèi)所有的指定構(gòu)造器。
如果子類(lèi)提供了所有父類(lèi)指定構(gòu)造器的實(shí)現(xiàn)——無(wú)論是通過(guò)規(guī)則 1 繼承過(guò)來(lái)的,還是提供了自定義實(shí)現(xiàn)——它將自動(dòng)繼承父類(lèi)所有的便利構(gòu)造器。
即使你在子類(lèi)中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用。
注意
對(duì)于規(guī)則 2,子類(lèi)可以將父類(lèi)的指定構(gòu)造器實(shí)現(xiàn)為便利構(gòu)造器。
接下來(lái)的例子將在實(shí)踐中展示指定構(gòu)造器、便利構(gòu)造器以及構(gòu)造器的自動(dòng)繼承。這個(gè)例子定義了包含三個(gè)類(lèi) Food
、RecipeIngredient
以及 ShoppingListItem
的類(lèi)層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。
類(lèi)層次中的基類(lèi)是 Food
,它是一個(gè)簡(jiǎn)單的用來(lái)封裝食物名字的類(lèi)。Food
類(lèi)引入了一個(gè)叫做 name
的 String
類(lèi)型的屬性,并且提供了兩個(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)造器鏈:
類(lèi)類(lèi)型沒(méi)有默認(rèn)的逐一成員構(gòu)造器,所以 Food
類(lèi)提供了一個(gè)接受單一參數(shù) name
的指定構(gòu)造器。這個(gè)構(gòu)造器可以使用一個(gè)特定的名字來(lái)創(chuàng)建新的 Food
實(shí)例:
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
Food
類(lèi)中的構(gòu)造器 init(name: String)
被定義為一個(gè)指定構(gòu)造器,因?yàn)樗艽_保 Food
實(shí)例的所有存儲(chǔ)型屬性都被初始化。Food
類(lèi)沒(méi)有父類(lèi),所以 init(name: String)
構(gòu)造器不需要調(diào)用 super.init()
來(lái)完成構(gòu)造過(guò)程。
Food
類(lèi)同樣提供了一個(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]
類(lèi)層級(jí)中的第二個(gè)類(lèi)是 Food
的子類(lèi) RecipeIngredient
。RecipeIngredient
類(lèi)用來(lái)表示食譜中的一項(xiàng)原料。它引入了 Int
類(lèi)型的屬性 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
類(lèi)的構(gòu)造器鏈:
RecipeIngredient
類(lèi)擁有一個(gè)指定構(gòu)造器 init(name: String, quantity: Int)
,它可以用來(lái)填充 RecipeIngredient
實(shí)例的所有屬性值。這個(gè)構(gòu)造器一開(kāi)始先將傳入的 quantity
參數(shù)賦值給 quantity
屬性,這個(gè)屬性也是唯一在 RecipeIngredient
中新引入的屬性。隨后,構(gòu)造器向上代理到父類(lèi) Food
的 init(name: String)
。這個(gè)過(guò)程滿(mǎn)足兩段式構(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í)例的 quantity
為 1
,所以不需要顯式指明數(shù)量即可創(chuàng)建出實(shí)例。這個(gè)便利構(gòu)造器的定義可以更加方便和快捷地創(chuàng)建實(shí)例,并且避免了創(chuàng)建多個(gè) quantity
為 1
的 RecipeIngredient
實(shí)例時(shí)的代碼重復(fù)。這個(gè)便利構(gòu)造器只是簡(jiǎn)單地橫向代理到類(lèi)中的指定構(gòu)造器,并為 quantity
參數(shù)傳遞 1
。
注意,RecipeIngredient
的便利構(gòu)造器 init(name: String)
使用了跟 Food
中指定構(gòu)造器 init(name: String)
相同的參數(shù)。由于這個(gè)便利構(gòu)造器重寫(xiě)了父類(lèi)的指定構(gòu)造器 init(name: String)
,因此必須在前面使用 override
修飾符(參見(jiàn)構(gòu)造器的繼承和重寫(xiě))。
盡管 RecipeIngredient
將父類(lèi)的指定構(gòu)造器重寫(xiě)為了便利構(gòu)造器,但是它依然提供了父類(lèi)的所有指定構(gòu)造器的實(shí)現(xiàn)。因此,RecipeIngredient
會(huì)自動(dòng)繼承父類(lèi)的所有便利構(gòu)造器。
在這個(gè)例子中,RecipeIngredient
的父類(lèi)是 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)
類(lèi)層級(jí)中第三個(gè)也是最后一個(gè)類(lèi)是 RecipeIngredient
的子類(lèi),叫做 ShoppingListItem
。這個(gè)類(lèi)構(gòu)建了購(gòu)物單中出現(xiàn)的某一種食譜原料。
購(gòu)物單中的每一項(xiàng)總是從未購(gòu)買(mǎi)狀態(tài)開(kāi)始的。為了呈現(xiàn)這一事實(shí),ShoppingListItem
引入了一個(gè) Boolean(布爾類(lèi)型) 的屬性 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)買(mǎi)。
由于它為自己引入的所有屬性都提供了默認(rèn)值,并且自己沒(méi)有定義任何構(gòu)造器,ShoppingListItem
將自動(dòng)繼承所有父類(lèi)中的指定構(gòu)造器和便利構(gòu)造器。
下圖展示了這三個(gè)類(lèi)的構(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ù)組的類(lèi)型也能被自動(dòng)推導(dǎo)為 [ShoppingListItem]
。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個(gè) ShoppingListItem
實(shí)例的名字從 [Unnamed]
更改為 Orange juice
,并標(biāo)記狀態(tài)為已購(gòu)買(mǎi)。打印數(shù)組中每個(gè)元素的描述顯示了它們都已按照預(yù)期被賦值。
如果一個(gè)類(lèi)、結(jié)構(gòu)體或枚舉類(lèi)型的對(duì)象,在構(gòu)造過(guò)程中有可能失敗,則為其定義一個(gè)可失敗構(gòu)造器是很有用的。這里所指的“失敗” 指的是,如給構(gòu)造器傳入無(wú)效的參數(shù)值,或缺少某種所需的外部資源,又或是不滿(mǎn)足某種必要的條件等。
為了妥善處理這種構(gòu)造過(guò)程中可能會(huì)失敗的情況。你可以在一個(gè)類(lèi),結(jié)構(gòu)體或是枚舉類(lèi)型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語(yǔ)法為在 init
關(guān)鍵字后面添加問(wèn)號(hào)(init?
)。
注意
可失敗構(gòu)造器的參數(shù)名和參數(shù)類(lèi)型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其參數(shù)類(lèi)型相同。
可失敗構(gòu)造器會(huì)創(chuàng)建一個(gè)類(lèi)型為自身類(lèi)型的可選類(lèi)型的對(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ù)字類(lèi)型轉(zhuǎn)換的可失敗構(gòu)造器。確保數(shù)字類(lèi)型之間的轉(zhuǎn)換能保持精確的值,使用這個(gè) init(exactly:)
構(gòu)造器。如果類(lèi)型轉(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? 類(lèi)型,不是 Int 類(lèi)型
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è)名為 species
的 String
類(lèi)型的常量屬性。同時(shí)該結(jié)構(gòu)體還定義了一個(gè)接受一個(gè)名為 species
的 String
類(lèi)型參數(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 的類(lèi)型是 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)造失?。?/p>
let anonymousCreature = Animal(species: "")
// anonymousCreature 的類(lèi)型是 Animal?, 而不是 Animal
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
注意
空字符串(如
""
,而不是"Giraffe"
)和一個(gè)值為nil
的可選類(lèi)型的字符串是兩個(gè)完全不同的概念。上例中的空字符串(""
)其實(shí)是一個(gè)有效的,非可選類(lèi)型的字符串。這里我們之所以讓Animal
的可失敗構(gòu)造器構(gòu)造失敗,只是因?yàn)閷?duì)于Animal
這個(gè)類(lèi)的species
屬性來(lái)說(shuō),它更適合有一個(gè)具體的值,而不是空字符串。
你可以通過(guò)一個(gè)帶一個(gè)或多個(gè)參數(shù)的可失敗構(gòu)造器來(lái)獲取枚舉類(lèi)型中特定的枚舉成員。如果提供的參數(shù)無(wú)法匹配任何枚舉成員,則構(gòu)造失敗。
下例中,定義了一個(gè)名為 TemperatureUnit
的枚舉類(lèi)型。其中包含了三個(gè)可能的枚舉成員(Kelvin
、Celsius
和 Fahrenheit
),以及一個(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."
帶原始值的枚舉類(lèi)型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:)
,該可失敗構(gòu)造器有一個(gè)名為 rawValue
的參數(shù),其類(lèi)型和枚舉類(lèi)型的原始值類(lèi)型一致,如果該參數(shù)的值能夠和某個(gè)枚舉成員的原始值匹配,則該構(gòu)造器會(huì)構(gòu)造相應(yīng)的枚舉成員,否則構(gòu)造失敗。
因此上面的 TemperatureUnit
的例子可以重寫(xiě)為:
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."
類(lèi),結(jié)構(gòu)體,枚舉的可失敗構(gòu)造器可以橫向代理到同類(lèi)型中的其他可失敗構(gòu)造器。