引言
繼續(xù)學(xué)習(xí)Swift文檔,從上一章節(jié):繼承衙荐,我們學(xué)習(xí)了Swift繼承相關(guān)的內(nèi)容膀息,如繼承的作用、重寫父類的方法和屬性(override關(guān)鍵詞)我纪、防止重寫(final關(guān)鍵詞)等這些內(nèi)容。現(xiàn)在,我們學(xué)習(xí)Swift的初始化相關(guān)的內(nèi)容浅悉。由于篇幅較長趟据,這里分篇來記錄,接下來术健,F(xiàn)ighting汹碱!
這一章節(jié)內(nèi)容有點(diǎn)冗長,理解即可荞估,熟悉的朋友可以移步下一章節(jié):析構(gòu)函數(shù)
初始化
初始化是準(zhǔn)備使用的類咳促,結(jié)構(gòu)體或枚舉實(shí)例的過程。 此過程涉及為該實(shí)例上的每個存儲屬性設(shè)置一個初始值勘伺,并執(zhí)行新實(shí)例準(zhǔn)備使用之前所需的任何其他設(shè)置或初始化跪腹。
您可以通過定義初始化程序來實(shí)現(xiàn)此初始化過程,初始化程序類似于可以調(diào)用以創(chuàng)建特定類型新實(shí)例的特殊方法飞醉。 與Objective-C初始化程序不同冲茸,Swift初始化程序不會返回值文搂。 它們的主要作用是確保在首次使用類型之前毅舆,正確初始化類型的新實(shí)例。
類類型的實(shí)例還可以實(shí)現(xiàn)一個反初始化器巫玻,該初始化器將在釋放該類的實(shí)例之前執(zhí)行任何自定義清除钦无。 有關(guān)Deinitialization的更多信息逗栽,請參見Deinitialization。
1 為存儲屬性設(shè)置初始值
類和結(jié)構(gòu)體必須在創(chuàng)建該類或結(jié)構(gòu)體的實(shí)例時將其所有存儲的屬性設(shè)置為適當(dāng)?shù)某跏贾怠?存儲的屬性不能處于不確定狀態(tài)失暂。
您可以在初始化程序中為存儲的屬性設(shè)置初始值彼宠,也可以通過將默認(rèn)屬性值分配為屬性定義的一部分來進(jìn)行設(shè)置。 以下各節(jié)介紹了這些操作趣席。
注意
當(dāng)您為存儲的屬性分配默認(rèn)值兵志,或在初始化程序中設(shè)置其初始值時,將直接設(shè)置該屬性的值宣肚,而無需調(diào)用任何屬性觀察器想罕。
1.1 初始化方法
調(diào)用初始化方法以創(chuàng)建特定類型的新實(shí)例。 最簡單的形式是霉涨,初始化方法就像沒有參數(shù)的實(shí)例方法按价,使用init關(guān)鍵字編寫:
init() {
// perform some initialization here
}
下面的示例定義了一個新的結(jié)構(gòu)體,稱為華氏溫度笙瑟,用于存儲以華氏度表示的溫度楼镐。 華氏結(jié)構(gòu)具有一個存儲的屬性,即溫度往枷,其類型為Double:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"
該結(jié)構(gòu)體定義了一個沒有參數(shù)的初始化程序init框产,該初始化方法將存儲的溫度初始化為32.0(水的冰點(diǎn)凄杯,以華氏度為單位)。
1.2 默認(rèn)屬性值
您可以從初始化方法中設(shè)置存儲屬性的初始值秉宿,如上所示戒突。 或者,在屬性聲明中指定默認(rèn)屬性值描睦。 您可以通過在定義屬性時為其分配初始值來指定默認(rèn)屬性值膊存。
注意
如果屬性始終使用相同的初始值,請?zhí)峁┠J(rèn)值忱叭,而不要在初始化方法中設(shè)置值隔崎。 最終結(jié)果是相同的,但是默認(rèn)值將屬性的初始化與聲明更緊密地聯(lián)系在一起韵丑。 它使初始化方法更短爵卒,更清晰,并使您能夠從其默認(rèn)值推斷屬性的類型埂息。 默認(rèn)值還使您更容易利用默認(rèn)初始化方法和初始化方法繼承技潘,如本章稍后所述。
您可以通過在聲明溫度屬性時為其溫度屬性提供默認(rèn)值的方式千康,從上方以更簡單的形式編寫華氏結(jié)構(gòu):
struct Fahrenheit {
var temperature = 32.0
}
2 自定義初始化
您可以使用輸入?yún)?shù)和可選屬性類型享幽,或通過在初始化過程中分配常量屬性來自定義初始化過程,如以下各節(jié)所述拾弃。
2.1 初始化參數(shù)
您可以在初始化方法定義的一部分中提供初始化參數(shù)值桩,以定義自定義初始化過程的值的類型和名稱。 初始化參數(shù)具有與函數(shù)和方法參數(shù)相同的功能和語法豪椿。
以下示例定義了一個稱為攝氏溫度的結(jié)構(gòu)體奔坟,該結(jié)構(gòu)體存儲以攝氏度表示的溫度。 Celsius結(jié)構(gòu)體實(shí)現(xiàn)了兩個名為init(fromFahrenheit :)和init(fromKelvin :)的自定義初始化方法搭盾,它們使用不同溫度范圍內(nèi)的值初始化該結(jié)構(gòu)體的新實(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 is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0
第一個初始化方法具有單個初始化參數(shù)咳秉,其參數(shù)標(biāo)簽為fromFahrenheit,參數(shù)名稱為fahrenheit鸯隅。 第二個初始化方法具有一個初始化參數(shù)澜建,其參數(shù)標(biāo)簽為fromKelvin,參數(shù)名稱為kelvin蝌以。 兩個初始化方法都將其單個參數(shù)轉(zhuǎn)換為相應(yīng)的攝氏值炕舵,并將該值存儲在名為temperatureInCelsius的屬性中。
2.2 參數(shù)名稱和參數(shù)標(biāo)簽
與函數(shù)和方法參數(shù)一樣跟畅,初始化參數(shù)可以具有用于初始化方法主體的參數(shù)名稱和用于調(diào)用初始化方法的參數(shù)標(biāo)簽咽筋。
但是,初始化方法在其括號前沒有以函數(shù)和方法那樣的方式標(biāo)識函數(shù)的名稱徊件。因此奸攻,初始化方法參數(shù)的名稱和類型在確定應(yīng)調(diào)用哪個初始化方法方面起著特別重要的作用蒜危。因此,如果您不提供初始化方法舞箍,則Swift會為初始化程序中的每個參數(shù)提供一個自動參數(shù)標(biāo)簽舰褪。
下面的示例定義一個名為Color的結(jié)構(gòu)體,具有三個不變的屬性疏橄,分別稱為red,green和blue略就。這些屬性存儲的值介于0.0和1.0之間捎迫,以指示顏色中紅色,綠色和藍(lán)色的數(shù)量表牢。
Color為初始化方法提供了三個適當(dāng)?shù)拿Q為Double類型的參數(shù)窄绒,作為其紅色,綠色和藍(lán)色分量崔兴。 Color還提供了帶有單個白色參數(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
}
}
通過為每個初始值設(shè)定項(xiàng)參數(shù)提供命名值敲茄,兩個初始值設(shè)定項(xiàng)均可用于創(chuàng)建新的Color實(shí)例:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
請注意位谋,如果不使用參數(shù)標(biāo)簽,則無法調(diào)用這些初始化方法堰燎。 如果已定義參數(shù)標(biāo)簽掏父,則必須始終在初始化方法中使用它們,而忽略它們是編譯時錯誤:
let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
2.3 沒有參數(shù)標(biāo)簽的初始化參數(shù)
如果不想為初始化參數(shù)使用參數(shù)標(biāo)簽秆剪,請為該參數(shù)寫下劃線(_)而不是顯式參數(shù)標(biāo)簽赊淑,以覆蓋默認(rèn)行為。
這是上述“初始化參數(shù)”中Celsius示例的擴(kuò)展版本仅讽,帶有一個附加的初始化f方法陶缺,可根據(jù)已經(jīng)在Celsius范圍內(nèi)的Double值創(chuàng)建一個新的Celsius實(shí)例:
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 is 37.0
初始化調(diào)用Celsius(37.0)的意圖很明確,不需要參數(shù)標(biāo)簽洁灵。 因此饱岸,將此初始化程序編寫為init(_ celsius:Double)是適當(dāng)?shù)模员憧梢酝ㄟ^提供未命名的Double值來調(diào)用它处渣。
2.4 可選屬性類型
如果您的自定義類型的存儲屬性在邏輯上被允許為“無值”(可能是因?yàn)樵诔跏蓟陂g無法設(shè)置其值伶贰,或者因?yàn)樯院竽硞€時候允許其具有“無值”),請使用 可選類型罐栈。 可選類型的屬性會自動使用nil值進(jìn)行初始化黍衙,這表明該屬性在初始化過程中故意有“沒有值”的意圖。
下面的示例定義一個名為SurveyQuestion的類荠诬,并帶有一個名為response的可選String屬性:
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()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
在詢問問題之前琅翻,無法知道對調(diào)查問題的回答位仁,因此,將響應(yīng)屬性聲明為String方椎?或“可選String”類型聂抢。 在初始化SurveyQuestion的新實(shí)例時,會自動為它分配默認(rèn)值nil棠众,表示“沒有字符串”琳疏。
2.5 初始化期間分配常量屬性
您可以在初始化期間的任何時候?yàn)槌A繉傩苑峙湟粋€值,只要在初始化完成時將其設(shè)置為確定值即可闸拿。 為常數(shù)屬性分配值后空盼,就無法再對其進(jìn)行修改。
注意
對于類實(shí)例新荤,只能在引入常量的類的初始化期間對其進(jìn)行修改揽趾。 子類不能修改它。
您可以從上面修改SurveyQuestion示例苛骨,以對問題的text屬性使用常量屬性而不是變量屬性篱瞎,以指示一旦創(chuàng)建SurveyQuestion的實(shí)例,問題就不會更改痒芝。 即使text屬性現(xiàn)在是常量俐筋,也可以在類的初始化程序中進(jìn)行設(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()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
3 默認(rèn)初始化方法
Swift為任何結(jié)構(gòu)體或類提供了默認(rèn)初始化方法,并且自身沒有定義初始化方法吼野。 默認(rèn)初始化方法僅創(chuàng)建一個新實(shí)例校哎,并將其所有屬性設(shè)置為默認(rèn)值。
本示例定義了一個名為ShoppingListItem的類瞳步,該類封裝了購物清單中商品的名稱闷哆,數(shù)量和購買狀態(tài):
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
因?yàn)镾hoppingListItem類的所有屬性都具有默認(rèn)值,并且由于它是沒有父類的基類单起,所以ShoppingListItem自動獲得一個默認(rèn)的初始化方法實(shí)現(xiàn)抱怔,該實(shí)現(xiàn)會創(chuàng)建一個將其所有屬性設(shè)置為其默認(rèn)值的新實(shí)例。 (name屬性是一個可選的String屬性嘀倒,即使該值未寫在代碼中屈留,它也會自動接收默認(rèn)值nil。)上面的示例對ShoppingListItem類使用默認(rèn)的初始化方法來創(chuàng)建ShoppingListItem類的新實(shí)例测蘑。 具有初始化語法的類灌危,編寫為ShoppingListItem(),并將此新實(shí)例分配給名為item的變量碳胳。
3.1 結(jié)構(gòu)體類型的成員初始化方法
如果結(jié)構(gòu)體類型未定義任何自己的自定義初始化方法勇蝙,則它們會自動收到一個成員初始化方法。 與默認(rèn)初始化方法不同的是挨约,該結(jié)構(gòu)體即使存儲了沒有默認(rèn)值的屬性味混,也會收到成員初始化方法产雹。
成員初始化方法是初始化新結(jié)構(gòu)體實(shí)例的成員屬性的簡便方法。 可以通過名稱將新實(shí)例的屬性的初始值傳遞給成員初始化方法翁锡。
下面的示例定義了一個名為Size的結(jié)構(gòu)蔓挖,具有兩個名為width和height的屬性。 通過指定默認(rèn)值0.0馆衔,可以推斷這兩個屬性均為Double類型瘟判。
Size結(jié)構(gòu)體自動接收一個init(width:height :)成員初始化方法,您可以使用它初始化一個新的Size實(shí)例:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
調(diào)用成員初始化方法時角溃,可以忽略具有默認(rèn)值的任何屬性的值荒适。 在上面的示例中,Size結(jié)構(gòu)體的height和width屬性均具有默認(rèn)值开镣。 您可以省略一個屬性或兩個屬性,并且初始化方法將對所有省略的內(nèi)容使用默認(rèn)值咽扇,例如:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"
4 值類型的初始化方法委托
初始化方法可以調(diào)用其他初始化方法來執(zhí)行實(shí)例初始化的一部分邪财。此過程稱為初始化方法委托,可避免在多個初始化方法之間重復(fù)代碼质欲。
對于值類型和類類型树埠,初始化方法委派的工作方式以及允許哪種形式的委派的規(guī)則是不同的。值類型(結(jié)構(gòu)體和枚舉)不支持繼承嘶伟,因此它們的初始化方法委托過程相對簡單怎憋,因?yàn)樗鼈冎荒芪薪o自己提供的另一個初始化方法。但是九昧,類可以從其他類繼承绊袋,如繼承中所述。這意味著類還有其他責(zé)任铸鹰,以確保在初始化期間為它們繼承的所有存儲屬性分配適當(dāng)?shù)闹蛋┍稹_@些職責(zé)在下面的類繼承和初始化中進(jìn)行了描述。
對于值類型蹋笼,在編寫自己的自定義初始化方法時展姐,可以使用self.init引用同一值類型的其他初始化方法。您只能在初始化程序中調(diào)用self.init剖毯。
請注意圾笨,如果您為值類型定義自定義初始化方法,則將不再有權(quán)使用該類型的默認(rèn)初始化方法(或成員初始化方法逊谋,如果它是結(jié)構(gòu)體)擂达。此約束防止了使用自動初始化方法之一的人意外繞過更復(fù)雜的初始化方法中提供的其他基本設(shè)置的情況。
注意
如果您希望自定義值類型可以使用默認(rèn)的初始值設(shè)定項(xiàng)和成員級初始值設(shè)定項(xiàng)以及自己的自定義初始值設(shè)定項(xiàng)進(jìn)行初始化涣狗,請?jiān)跀U(kuò)展名中編寫自定義初始值設(shè)定項(xiàng)谍婉,而不是將其作為值類型原始實(shí)現(xiàn)的一部分舒憾。 有關(guān)更多信息,請參見Extensions穗熬。
以下示例定義了一個自定義的Rect結(jié)構(gòu)體來表示一個幾何矩形镀迂。 該示例需要兩個支持的結(jié)構(gòu)體,分別稱為“大小”和“點(diǎn)”唤蔗,這兩個結(jié)構(gòu)體的所有屬性均提供默認(rèn)值0.0:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
您可以通過以下三種方式之一來初始化Rect結(jié)構(gòu)體:使用其默認(rèn)的零初始化原點(diǎn)和尺寸屬性值探遵,提供特定的原點(diǎn)和尺寸,或提供特定的中心點(diǎn)和尺寸妓柜。 這些初始化選項(xiàng)由三個自定義的初始化方法表示箱季,它們是Rect結(jié)構(gòu)體定義的一部分:
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
第一個Rect初始化程序init()在功能上與該結(jié)構(gòu)體(如果沒有自己的自定義初始化方法)將收到的默認(rèn)初始化方法相同。 此初始化方法的主體為空棍掐,由一對空的花括號{}表示藏雏。 調(diào)用此初始化方法將返回一個Rect實(shí)例,該實(shí)例的origin和size屬性均使用其屬性定義中的Point(x:0.0作煌,y:0.0)和Size(width:0.0掘殴,height:0.0)的默認(rèn)值初始化:
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
第二個Rect初始化方法init(origin:size :)在功能上與該結(jié)構(gòu)體(如果沒有自己的自定義初始化方法)將收到的成員初始化方法相同。 該初始化方法只是將origin和size參數(shù)值分配給適當(dāng)?shù)拇鎯傩裕?/p>
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
第三個Rect初始化方法init(center:size :)稍微復(fù)雜一些粟誓。 首先根據(jù)中心點(diǎn)和大小值計(jì)算適當(dāng)?shù)脑c(diǎn)奏寨。 然后,它調(diào)用(或委托)init(origin:size :)初始化方法鹰服,該初始化方法將新的origin和size值存儲在適當(dāng)?shù)膶傩灾校?/p>
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
init(center:size :)初始化方法可能已將原始值和大小的新值分配給了適當(dāng)?shù)膶傩员旧怼?但是病瞳,init(center:size :)初始化方法比利用已經(jīng)提供了該功能的現(xiàn)有初始化方法更為方便(意圖更清晰)。
注意
有關(guān)無需親自定義init()和init(origin:size :)初始化方法的另一示例編寫方式悲酷,請參見Extensions套菜。
5 類繼承和初始化
在初始化期間,必須為類的所有存儲屬性(包括該類從其父類繼承的所有屬性)分配一個初始值舔涎。
Swift為類類型定義了兩種初始化方法笼踩,以幫助確保所有存儲的屬性均接收初始值。 這些被稱為指定的初始化方法和便捷初始化方法亡嫌。
5.1 指定的初始化方法和便捷初始化方法
指定的初始化方法是類的主要初始化方法嚎于。 指定的初始化方法將完全初始化該類引入的所有屬性,并調(diào)用適當(dāng)?shù)母割惓跏蓟椒ㄒ岳^續(xù)父類鏈中的初始化過程挟冠。
類往往只有很少的指定初始化方法于购,而一個類通常只有一個。 指定的初始化方法是“funnel”點(diǎn)知染,通過該“funnel”點(diǎn)進(jìn)行初始化肋僧,并通過該“funnel”點(diǎn)繼續(xù)父類鏈中的初始化過程。
每個類必須至少有一個指定的初始化方法。 在某些情況下嫌吠,可以通過從父類繼承一個或多個指定的初始化方法來滿足此要求止潘,如下面的Automatic Initializer Inheritance中所述。
便利的初始值設(shè)定項(xiàng)是輔助的辫诅,支持類的初始值設(shè)定項(xiàng)凭戴。 您可以定義一個便捷初始化方法,以從與便捷初始化方法相同的類中調(diào)用一個指定初始化方法炕矮,并將某些指定初始值設(shè)定項(xiàng)的參數(shù)設(shè)置為默認(rèn)值么夫。 您還可以定義一個便捷初始化方法,以針對特定用例或輸入值類型創(chuàng)建該類的實(shí)例肤视。
如果您的類不需要便利初始化方法档痪,則不必提供它們。 只要通向通用初始化模式的快捷方式可以節(jié)省時間或使類的初始化更清晰邢滑,就可以創(chuàng)建方便的初始化方法腐螟。
5.2 指定的和便捷的初始化語法
指定的類初始化方法的編寫方式與值類型的簡單初始化方法的編寫方式相同:
init(parameters) {
statements
}
便捷初始化程序以相同的樣式編寫,但是在便捷關(guān)鍵字init關(guān)鍵字之前放置了convenience修飾符困后,并用空格分隔:
convenience init(parameters) {
statements
}
5.3 類類型的初始化方法委托
為了簡化指定初始化方法和便捷初始化方法之間的關(guān)系遭垛,Swift將以下三個規(guī)則應(yīng)用于調(diào)用初始化方法之間的委托:
規(guī)則1
指定的初始值設(shè)定項(xiàng)必須從其直接父類調(diào)用指定的初始值設(shè)定項(xiàng)。
規(guī)則2
便捷初始化方法必須從同一個類里調(diào)用另一個初始化方法操灿。
規(guī)則3
便利初始化方法必須最終調(diào)用指定的初始化方法。
記住這個的一個簡便方法:
- 指定的初始化方法必須始終委托泵督。
- 便捷初始化方法必須始終委派趾盐。
這些規(guī)則如下圖所示:
在這里,父類具有一個指定的初始值設(shè)定項(xiàng)和兩個便利的初始化項(xiàng)小腊。 一個便利初始化方法調(diào)用另一個便利初始化方法救鲤,后者又調(diào)用單個指定的初始化方法。 這從上方滿足規(guī)則2和3秩冈。 父類本身沒有其他父類本缠,因此規(guī)則1不適用。
該圖中的子類具有兩個指定的初始化方法和一個便捷的初始化方法入问。 便捷初始化方法必須調(diào)用兩個指定的初始化方法之一丹锹,因?yàn)樗荒苷{(diào)用同一類中的另一個初始化方法。 這從上方滿足規(guī)則2和3芬失。 兩個指定的初始值設(shè)定項(xiàng)都必須從父類中調(diào)用單個指定的初始值設(shè)定項(xiàng)楣黍,以滿足上方的規(guī)則1。
注意
這些規(guī)則不會影響classes用戶如何創(chuàng)建每個class的實(shí)例棱烂。 上圖中的任何初始化方法都可用于創(chuàng)建它們所屬類的完全初始化的實(shí)例租漂。 這些規(guī)則只會影響您如何編寫類的初始化方法的實(shí)現(xiàn)。
下圖顯示了四個類的更復(fù)雜的類層次結(jié)構(gòu)。 它說明了此層次結(jié)構(gòu)中指定的初始化方法如何充當(dāng)類初始化的“funnel”點(diǎn)哩治,從而簡化了鏈中各類之間的相互關(guān)系:
5.4 初始化的兩個階段
Swift中的類初始化是一個分為兩個階段的過程秃踩。 在第一階段,每個存儲的屬性都由引入它的類分配一個初始值业筏。 一旦確定了每個存儲屬性的初始狀態(tài)憔杨,便開始第二階段,并且在認(rèn)為新實(shí)例可以使用之前驾孔,每個類都有機(jī)會自定義其存儲屬性芍秆。
兩階段初始化過程的使用使初始化安全,同時仍為類層次結(jié)構(gòu)中的每個類提供了完全的靈活性翠勉。 兩階段初始化可防止在初始化屬性值之前對其進(jìn)行訪問妖啥,并防止其他初始化方法意外地將屬性值設(shè)置為其他值。
注意
Swift的兩階段初始化過程類似于Objective-C中的初始化对碌。 主要區(qū)別在于荆虱,在階段1中,Objective-C為每個屬性分配零或空值(例如0或nil)朽们。 Swift的初始化流程更加靈活怀读,因?yàn)樗梢宰屇O(shè)置自定義初始值,并且可以處理0或nil不是有效默認(rèn)值的類型骑脱。
Swift的編譯器會執(zhí)行四項(xiàng)有用的安全檢查菜枷,以確保兩階段初始化完成且沒有錯誤:
安全檢查1
指定的初始值設(shè)定項(xiàng)必須確保由其類引入的所有屬性在委托給父類初始值設(shè)定項(xiàng)之前都已初始化。
如上所述叁丧,僅在知道對象所有存儲屬性的初始狀態(tài)后啤誊,才認(rèn)為該對象的內(nèi)存已完全初始化。 為了滿足此規(guī)則拥娄,指定的初始值設(shè)定項(xiàng)必須確保在傳遞鏈之前初始化其自身的所有屬性蚊锹。
安全檢查2
在將值分配給繼承的屬性之前,指定的初始值設(shè)定項(xiàng)必須委托一個父類初始值設(shè)定項(xiàng)稚瘾。 如果不是這樣牡昆,則指定的初始值設(shè)定項(xiàng)分配的新值將被父類覆蓋,作為其自身初始化的一部分摊欠。
安全檢查3
便利初始化方法必須在將值分配給任何屬性(包括由同一類定義的屬性)之前委托給另一個初始化方法丢烘。 如果不是,便利初始化方法分配的新值將被其自己class指定的初始化方法覆蓋些椒。
安全檢查4
在初始化的第一階段完成之前铅协,初始化方法無法調(diào)用任何實(shí)例方法,讀取任何實(shí)例屬性的值或?qū)elf作為值摊沉。
在第一階段結(jié)束之前狐史,該類實(shí)例并不完全有效。 一旦在第一階段結(jié)束時知道類實(shí)例是有效的,就只能訪問屬性骏全,并且只能調(diào)用方法苍柏。
根據(jù)上述四項(xiàng)安全檢查,以下是兩階段初始化的結(jié)果:
階段1
- 在類上調(diào)用了指定的或便捷的初始化方法姜贡。
- 分配該類的新實(shí)例的內(nèi)存试吁。 內(nèi)存尚未初始化。
- 該類的指定初始化方法確認(rèn)該類引入的所有存儲屬性都具有值楼咳。
- 這些存儲的屬性的內(nèi)存現(xiàn)在已初始化熄捍。
- 指定的初始值設(shè)定項(xiàng)移交給父類初始值設(shè)定項(xiàng),以其自身的存儲屬性執(zhí)行相同的任務(wù)母怜。
- 這將繼續(xù)類繼承鏈余耽,直到到達(dá)鏈的頂部。
- 一旦到達(dá)鏈的頂部苹熏,并且鏈中的最后一個類確保其所有存儲的屬性都具有值碟贾,則實(shí)例的內(nèi)存將被視為已完全初始化,并且階段1已完成轨域。
階段2
- 從鏈的頂部向下追溯袱耽,鏈中的每個指定的初始化方法都可以選擇進(jìn)一步自定義實(shí)例。 初始化方法現(xiàn)在可以訪問self干发,并且可以修改其屬性朱巨,調(diào)用其實(shí)例方法等等。
- 最后枉长,鏈中的所有便利初始化方法都可以選擇自定義實(shí)例并與self一起使用蔬崩。
第1階段查找假設(shè)的子類和父類的初始化調(diào)用的方式如下:
在此示例中,初始化始于對子類的便捷初始化方法的調(diào)用搀暑。 此便捷初始化方法尚無法修改任何屬性。 它委托來自同一類的指定初始化方法跨琳。
根據(jù)安全檢查1自点,指定的初始值設(shè)定項(xiàng)可確保子類的所有屬性都有一個值。然后脉让,它在其父類上調(diào)用指定的初始值設(shè)定項(xiàng)以繼續(xù)鏈上的初始化桂敛。
父類的指定初始值設(shè)定項(xiàng)可確保所有父類屬性都有一個值。 沒有其他要初始化的父類溅潜,因此不需要進(jìn)一步的委派术唬。
一旦父類的所有屬性都具有初始值,就將其內(nèi)存視為已完全初始化滚澜,并且階段1已完成粗仓。
第2階段查找相同的初始化調(diào)用的方式如下:
現(xiàn)在,父類的指定初始化方法有機(jī)會進(jìn)一步自定義實(shí)例(盡管不必如此)。
一旦父類的指定初始值設(shè)定項(xiàng)完成借浊,子類的指定初始值設(shè)定項(xiàng)即可執(zhí)行其他自定義操作(盡管同樣塘淑,它不必這樣做)。
最后蚂斤,子類的指定初始化方法完成后存捺,最初調(diào)用的便捷初始化方法可以執(zhí)行其他自定義曙蒸。
5.5 初始化方法的繼承和重寫
與Objective-C中的子類不同,Swift子類默認(rèn)情況下不會繼承其父類初始化方法纽窟。 Swift的方法可以防止這樣的情況,即父類的簡單初始化方法被更專門的子類繼承师倔,并用于創(chuàng)建未完全或正確初始化的子類的新實(shí)例。
注意
父類初始化方法在某些情況下會被繼承趋艘,但是只有在安全且適當(dāng)?shù)那闆r下才可以這樣做疲恢。 有關(guān)更多信息,請參見下面的Automatic Initializer Inheritance瓷胧。
如果希望自定義子類提供一個或多個與其父類相同的初始化方法显拳,則可以在子類中提供這些初始化方法的自定義實(shí)現(xiàn)。
當(dāng)編寫與父類指定的初始化方法匹配的子類初始化方法時搓萧,實(shí)際上是在提供該指定的初始化方法的替代杂数。 因此,您必須在子類的初始值設(shè)定項(xiàng)定義之前編寫override修飾符瘸洛。 即使您要重寫自動提供的默認(rèn)初始化方法揍移,也是如此,如默認(rèn)初始化方法中所述反肋。
與重寫屬性那伐,方法或下標(biāo)一樣,override修飾符的存在會提示Swift檢查父類是否具有匹配的指定初始化方法要被重寫石蔗,并驗(yàn)證是否已按預(yù)期指定了重寫初始化器的參數(shù)罕邀。
注意
重寫父類指定的初始值設(shè)定項(xiàng)時,即使您子類的初始值設(shè)定實(shí)現(xiàn)是便捷的初始值設(shè)定項(xiàng)养距,您也始終要編寫override修飾符诉探。
相反,如果您編寫與父類便利性初始化方法匹配的子類初始化方法棍厌,則根據(jù)上面的“類類型的初始化方法委托”中所述的規(guī)則肾胯,您的子類將永遠(yuǎn)無法直接調(diào)用該父類便利性初始化方法竖席。 因此阳液,您的子類(嚴(yán)格地說)沒有提供父類初始值設(shè)定項(xiàng)的替代帘皿。 因此,在提供父類便捷初始化方法的匹配實(shí)現(xiàn)時虽填,您無需編寫override修飾符曹动。
下面的示例定義了一個稱為Vehicle的基類墓陈。 此基類聲明一個稱為numberOfWheels的存儲屬性,默認(rèn)Int值為0兔港。numberOfWheels屬性由稱為description的計(jì)算屬性使用仔拟,以創(chuàng)建有關(guān)車輛特性的String描述:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle類為其唯一存儲的屬性提供默認(rèn)值利花,并且本身不提供任何自定義初始化方法。 結(jié)果臀栈,它會自動接收默認(rèn)初始化方法权薯,如默認(rèn)初始化方法中所述窄刘。 默認(rèn)的初始值設(shè)定項(xiàng)(如果可用)始終是類的指定初始值設(shè)定項(xiàng),可用于創(chuàng)建numberOfWheels為0的新Vehicle實(shí)例:
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
下一個示例定義了Vehicle的子類,稱為Bicycle:
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
Bicycle子類定義了一個自定義的初始化方法init()橄仍。 此指定的初始值設(shè)定項(xiàng)與Bicycle的父類中的指定的初始值設(shè)定項(xiàng)匹配,因此此初始值設(shè)定項(xiàng)的Bicycle版本用override修飾符標(biāo)記虑粥。
Bicycle的init()初始值設(shè)定項(xiàng)始于調(diào)用super.init(),super.init()會為Bicycle類的父類Vehicle調(diào)用默認(rèn)的初始值設(shè)定項(xiàng)第晰。 這樣可以確保在Bicycle可以修改屬性之前茁瘦,由Vehicle初始化numberOfWheels繼承的屬性储笑。 調(diào)用super.init()之后突倍,將numberOfWheels的原始值替換為新值2。
如果創(chuàng)建Bicycle的實(shí)例焊虏,則可以調(diào)用其繼承的描述計(jì)算屬性炕淮,以查看其numberOfWheels屬性的更新方式:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
如果子類初始化方法在初始化過程的第2階段不進(jìn)行自定義跳夭,并且父類具有零參數(shù)指定的初始化方法,則可以在將值分配給所有子類的所有存儲屬性后润歉,省略對super.init()的調(diào)用。
本示例定義了Vehicle的另一個子類踩衩,稱為Hoverboard驱富。 在其初始化方法中匹舞,Hoverboard類僅設(shè)置其color屬性赐稽。 該初始化方法沒有顯式調(diào)用super.init()浑侥,而是依靠對其父類的初始化程序的隱式調(diào)用來完成該過程寓落。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() implicitly called here
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}
Hoverboard的實(shí)例使用Vehicle初始化方法提供的默認(rèn)車輪數(shù)伶选。
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
注意
子類可以在初始化期間修改繼承的變量屬性考蕾,但不能修改繼承的常量屬性会宪。
5.6 自動初始化方法繼承
如上所述,默認(rèn)情況下塞帐,子類不繼承其父類初始化方法葵姥。 但是句携,如果滿足某些條件,則會自動繼承父類初始化方法削咆。 實(shí)際上拨齐,這意味著您不需要在許多常見情況下編寫初始化方法覆蓋瞻惋,并且可以在安全的情況下以最小的努力繼承父類初始化方法援岩。
假設(shè)為子類中引入的任何新屬性提供默認(rèn)值,則適用以下兩個規(guī)則:
規(guī)則1
如果您的子類沒有定義任何指定的初始值設(shè)定項(xiàng)羽峰,它將自動繼承其所有父類指定的初始值設(shè)定項(xiàng)限寞。
規(guī)則2
如果您的子類提供了其所有父類指定初始化方法的實(shí)現(xiàn)(通過按規(guī)則1繼承它們履植,或通過提供自定義實(shí)現(xiàn)作為其定義的一部分)悄晃,那么它將自動繼承所有父類便利初始化方法妈橄。
即使您的子類添加了進(jìn)一步的便利初始化方法,這些規(guī)則也適用鼻种。
注意
子類可以將父類指定的初始化方法實(shí)現(xiàn)為子類便捷初始化方法沙热,作為滿足規(guī)則2的一部分。
5.7 指定的和便捷的初始化方法的實(shí)現(xiàn)
以下示例顯示了實(shí)際的指定初始化方法投队,便捷初始化方法和自動初始化方法繼承敷鸦。 此示例定義了三個類的層次結(jié)構(gòu)扒披,分別稱為Food兔甘,RecipeIngredient和ShoppingListItem,并演示了它們的初始化方法如何交互蟆淀。
層次結(jié)構(gòu)中的基類稱為Food熔任,它是封裝食品名稱的簡單類疑苔。 Food類引入了一個名為name的String屬性甸鸟,并提供了兩個用于創(chuàng)建Food實(shí)例的初始化方法:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
下圖顯示了Food類的初始化方法鏈:
類沒有默認(rèn)的成員初始化方法,因此Food類提供了一個指定的初始化方法薪贫,該初始化方法帶有一個名為name的參數(shù)瞧省。 此初始化方法可用于創(chuàng)建具有特定名稱的新Food實(shí)例:
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
提供Food類的init(name:String)初始化方法作為指定的初始化方法,因?yàn)樗纱_保新Food實(shí)例的所有存儲屬性都已完全初始化交洗。 Food類沒有父類构拳,因此init(name:String)初始化方法不需要調(diào)用super.init()即可完成其初始化梁棠。
Food類還提供了一個沒有參數(shù)的便捷初始化方法init()掰茶。 init()初始值設(shè)定項(xiàng)委派給Food類的init(name: String),名稱值為[Unnamed]盐碱,從而為新食品提供默認(rèn)的占位符名稱:
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
層次結(jié)構(gòu)中的第二個類是Food的子類瓮顽,名為RecipeIngredient围橡。 RecipeIngredient類對烹飪食譜中的成分進(jìn)行建模翁授。 它引入了一個稱為數(shù)量的Int屬性(除了它從Food繼承的name屬性之外),并定義了兩個用于創(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類的初始化程序鏈:
RecipeIngredient類具有單個指定的初始化方法init(name:String贮配,Quantity:Int)泪勒,可用于填充新RecipeIngredient實(shí)例的所有屬性圆存。該初始化方法首先將傳遞的數(shù)量參數(shù)分配給數(shù)量屬性沦辙,此屬性是RecipeIngredient引入的唯一新屬性。這樣做之后,初始化方法將委托給Food類的init(name:String)初始化方法撞羽。該過程滿足上述兩階段初始化的安全檢查1衫冻。
RecipeIngredient還定義了一個便利的初始化程方法init(name:String)隅俘,該初始化方法僅用于按名稱創(chuàng)建RecipeIngredient實(shí)例。對于任何沒有顯式數(shù)量創(chuàng)建的RecipeIngredient實(shí)例碌宴,此便利初始化方法均假設(shè)其數(shù)量為1贰镣。此便捷初始化方法的定義使RecipeIngredient實(shí)例創(chuàng)建起來更快膳凝,更方便蹬音,并且在創(chuàng)建多個單量RecipeIngredient實(shí)例時避免了代碼重復(fù)。此便捷的初始化方法僅將數(shù)量值1傳遞給該類的指定的初始化方法劫狠。
RecipeIngredient提供的init(name:String)便利初始化方法采用與Food中指定的init(name:String)初始化方法相同的參數(shù)嘉熊。因?yàn)榇吮憬莩跏蓟椒〞貙懫涓割愔械闹付ǔ跏蓟椒ㄑ锸妫员仨毷褂胦verride修飾符對其進(jìn)行標(biāo)記(如Initializer Inheritance and Overriding中所述)。
盡管RecipeIngredient提供了init(name:String)初始化方法作為便利的初始化方法愧薛,但RecipeIngredient仍提供了其所有父類指定的初始化方法的實(shí)現(xiàn)衫画。因此削罩,RecipeIngredient也會自動繼承其所有父類的便利初始化方法。
在此示例中进陡,RecipeIngredient的父類是Food微服,它具有一個名為init()的便捷初始化方法以蕴。因此,此初始值設(shè)定項(xiàng)由RecipeIngredient繼承赡磅。 init()的繼承版本的功能與Food版本完全相同仆邓,不同之處在于伴鳖,它委派給init(name:String)的RecipeIngredient版本而不是Food版本榜聂。
這三個初始化方法均可用于創(chuàng)建新的RecipeIngredient實(shí)例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
層次結(jié)構(gòu)中的第三個也是最后一個類是RecipeIngredient的子類,稱為ShoppingListItem匿乃。 ShoppingListItem類對食譜成分進(jìn)行建模幢炸,使其顯示在購物清單中拒贱。
購物清單中的每個項(xiàng)目都以“未購買”開始。 為了表示這一事實(shí)闸天,ShoppingListItem引入了一個布爾屬性苞氮,稱為purchaded,默認(rèn)值為false库物。 ShoppingListItem還添加了一個計(jì)算的描述屬性戚揭,該屬性提供ShoppingListItem實(shí)例的文本描述:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ?" : " ?"
return output
}
}
注意
ShoppingListItem并未定義初始化方法來為購買的商品提供初始值皿桑,因?yàn)橘徫锴鍐沃械纳唐罚ㄈ绱颂幗#┦冀K始于未購買的商品诲侮。
因?yàn)樗鼮樗氲乃袑傩蕴峁┝四J(rèn)值沟绪,并且本身沒有定義任何初始化方法绽慈,所以ShoppingListItem會自動從其父類繼承所有指定的便捷初始化方法辈毯。
下圖顯示了所有三個類的整體初始化方法鏈:
您可以使用所有繼承的三個初始化方法來創(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 ?
在這里谆沃,從包含三個新ShoppingListItem實(shí)例的數(shù)組文字創(chuàng)建了一個名為BreakfastList的新數(shù)組唁影。 數(shù)組的類型推斷為[ShoppingListItem]。 創(chuàng)建數(shù)組后哟沫,數(shù)組開頭的ShoppingListItem的名稱從“ [未命名]”更改為“橙汁”嗜诀,并標(biāo)記為已購買。 打印陣列中每個項(xiàng)目的描述將表明它們的默認(rèn)狀態(tài)已按預(yù)期設(shè)置肿嘲。
6 初始化失敗
有時筑公,定義初始化可能失敗的類匣屡,結(jié)構(gòu)體或枚舉有時很有用。 無效的初始化參數(shù)值誉结,缺少必需的外部資源或其他阻止初始化成功的條件可能觸發(fā)此失敗惩坑。
為了應(yīng)對可能失敗的初始化條件以舒,請將一個或多個可失敗的初始化方法定義為類慢哈,結(jié)構(gòu)體或枚舉定義的一部分卵贱。 通過在init關(guān)鍵字(init?)后面放置問號兰绣,可以編寫失敗的初始化方法狭魂。
注意
您不能使用相同的參數(shù)類型和名稱來定義可失敗的初始化方法和不可失敗的初始化方法党觅。
失敗的初始化方法會創(chuàng)建一個初始化類型的可選值杯瞻。 您在失敗的初始值設(shè)定項(xiàng)中寫入return nil,以指示可以觸發(fā)初始化失敗的點(diǎn)睬涧。
注意
嚴(yán)格來說,初始方法不返回值痹束。 相反祷嘶,它們的作用是確保在初始化結(jié)束時完全正確地初始化自身夺溢。 盡管您編寫了return nil來觸發(fā)初始化失敗风响,但是您并未使用return關(guān)鍵字來指示初始化成功。
例如鞋怀,為數(shù)字類型轉(zhuǎn)換實(shí)現(xiàn)了失敗的初始化方法密似。 為了確保數(shù)值類型之間的轉(zhuǎn)換可以準(zhǔn)確地保持值朵诫,請使用init(exactly :)初始化方法薄扁。 如果類型轉(zhuǎn)換不能保持該值邓梅,則初始化方法將失敗。
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"
下面的示例定義了一個名為Animal的結(jié)構(gòu)體钱反,該結(jié)構(gòu)體具有一個恒定的String屬性面哥,稱為species尚卫。 Animal結(jié)構(gòu)體還定義了一個失敗的初始值設(shè)定項(xiàng)尸红,該初始設(shè)定項(xiàng)具有一個稱為species的單個參數(shù)。 此初始化方法檢查傳遞給初始化方法的種類值是否為空字符串怎爵。 如果找到空字符串鳖链,則會觸發(fā)初始化失敗。 否則乞旦,將設(shè)置種類屬性的值兰粉,并且初始化成功:
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
您可以使用此失敗的初始化方法嘗試初始化新的Animal實(shí)例顶瞳,并檢查初始化是否成功:
let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"
如果您將空字符串值傳遞給失敗的初始化方法的species參數(shù)慨菱,則初始化方法會觸發(fā)初始化失敺取:
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"
注意
檢查空字符串值(例如“”而不是“ Giraffe”)與檢查nil(表示沒有可選的String值)不同。 在上面的示例中畏腕,空字符串(“”)是有效的非可選字符串描馅。 但是铭污,動物不宜使用空字符串作為其物種屬性的值膀篮。 為了對此限制建模,如果發(fā)現(xiàn)空字符串磅网,則可失敗的初始化程序?qū)⒂|發(fā)初始化失敗知市。
6.1 枚舉初始化失敗
您可以使用故障初始化方法基于一個或多個參數(shù)來選擇適當(dāng)?shù)拿杜e用例。 如果提供的參數(shù)與適當(dāng)?shù)拿杜ecase不匹配娘赴,則初始化方法可能會失敗跟啤。
下面的示例定義了一個名為TemperatureUnit的枚舉隅肥,具有三個可能的狀態(tài)(kelvin,celsius和fahrenheit)泛啸。 一個有故障的初始化方法用于為代表溫度符號的Character值找到合適的枚舉形式:
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
}
}
}
您可以使用此故障初始化方法為三種可能的狀態(tài)選擇合適的枚舉case候址,并在參數(shù)與以下狀態(tài)之一不匹配時導(dǎo)致初始化失敻诼亍:
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "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.")
}
// Prints "This is not a defined temperature unit, so initialization failed."
6.2 帶有原始值的枚舉初始化失敗
帶有原始值的枚舉會自動接收一個失敗的初始化方法init荠雕?(rawValue :)驶赏,該初始化方法將使用一個稱為RawValue的參數(shù)母市,該參數(shù)具有適當(dāng)?shù)脑贾殿愋退鹎鳎⑶胰绻业揭粋€匹配的枚舉浑槽,則選擇一個匹配的枚舉;如果沒有匹配的值篙挽,則觸發(fā)初始化失敗铣卡。
您可以從上面重寫TemperatureUnit示例,以使用Character類型的原始值并利用init敞峭?(rawValue :)初始化方法的優(yō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.")
}
// Prints "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.")
}
// Prints "This is not a defined temperature unit, so initialization failed."
6.3 初始化失敗的傳遞
一個類旋讹,結(jié)構(gòu)體或枚舉的故障初始化方法可以委托同一個類轿衔,結(jié)構(gòu)體或枚舉的另一個故障初始化方法害驹。 類似地,子類可故障初始化方法可以委托父類可故障初始化方法琅拌。
在任何一種情況下进宝,如果委托給另一個導(dǎo)致初始化失敗的初始化方法枷恕,則整個初始化過程將立即失敗,并且不會執(zhí)行其他初始化代碼未玻。
注意
一個失敗的初始化方法也可以委派給一個不失敗的初始化方法。 如果您需要將潛在的失敗狀態(tài)添加到不會失敗的現(xiàn)有初始化過程中扳剿,請使用此方法庇绽。
下面的示例定義了一個名為CartItem的Product子類瞧掺。 CartItem類為在線購物車中的商品建模辟狈。 CartItem引入了一個存儲的常量屬性,稱為數(shù)量明未,并確保該屬性的值始終至少為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的失敗初始值設(shè)定項(xiàng)始于驗(yàn)證它收到的數(shù)量值為1或更大亚隅。 如果數(shù)量無效煮纵,則整個初始化過程將立即失敗偏螺,并且不再執(zhí)行任何初始化代碼套像。 同樣,Product的失敗初始化方法將檢查名稱值贞让,如果name為空字符串喳张,則初始化方法將立即失敗销部。
如果使用非空名稱創(chuàng)建數(shù)量為1或更大的CartItem實(shí)例制跟,則初始化成功:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
如果嘗試創(chuàng)建數(shù)量值為0的CartItem實(shí)例雨膨,則CartItem初始化程序?qū)?dǎo)致初始化失斄募恰:
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
同樣,如果您嘗試創(chuàng)建一個名稱值為空的CartItem實(shí)例踩身,則父類Product初始化方法會導(dǎo)致初始化失斏缏丁:
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"
6.4 重寫初始化失敗
您可以在子類中重寫父類可失敗的初始化方法峭弟,就像其他任何初始化方法一樣瞒瘸。 或者,您可以使用子類不可失敗的初始化方法來覆蓋父類可失敗的初始化方法省撑。 這使您可以定義一個子類竟秫,即使允許父類的初始化失敗肥败,其初始化也不會失敗愕提。
請注意浅侨,如果使用不可失敗的子類初始化方法重寫了可失敗的父類初始化方法,則委派給父類初始化方法的唯一方法是強(qiáng)制展開可失敗的父類初始化方法的結(jié)果佛舱。
注意
您可以使用不可失敗的初始值設(shè)定項(xiàng)來重寫可失敗的初始設(shè)定項(xiàng)请祖,但反之則不能脖祈。
下面的示例定義了一個稱為Document的類盖高。 此類對可以使用name屬性初始化的文檔進(jìn)行建模,該name屬性可以是非空字符串值或nil席纽,但不能為空字符串:
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
下一個示例定義了一個名為AutomaticallyNamedDocument的Document子類润梯。 AutomaticallyNamedDocument子類將重寫Document引入的兩個指定的初始化方法纺铭。 這些重寫可確保如果實(shí)例初始化時不帶名稱舶赔,或者將空字符串傳遞給init(name :)初始化方法,則AutomaticallyNamedDocument實(shí)例的初始名稱值為“ [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使用不可失敗的init(name :)初始值設(shè)定項(xiàng)重寫其父類的init可失敗的init撵溃?(name :)初始值設(shè)定項(xiàng)征懈。 因?yàn)锳utomaticallyNamedDocument以不同于其父類的方式處理空字符串大小寫卖哎,所以其初始化方法不需要失敗亏娜,因此它提供了初始化方法的非失敗版本蹬挺。
您可以在初始值設(shè)定項(xiàng)中使用強(qiáng)制展開巴帮,以從父類中調(diào)用失敗的初始值設(shè)定項(xiàng),這是子類的非失敗初始值設(shè)定項(xiàng)的實(shí)現(xiàn)的一部分垃沦。 例如肢簿,下面的UntitledDocument子類始終被命名為“ [Untitled]”蜻拨,并且它在初始化過程中使用其父類中的失敗init(name :)初始化方法缎讼。
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在這種情況下,如果曾經(jīng)用空字符串作為名稱調(diào)用父類的init(name :)初始化方法卧惜,則強(qiáng)制展開操作將導(dǎo)致運(yùn)行時錯誤。 但是手幢,由于使用字符串常量調(diào)用了它忱详,因此您可以看到初始化方法不會失敗匈睁,因此在這種情況下不會發(fā)生運(yùn)行時錯誤航唆。
6.5 init!初始化失敗
通常院刁,您可以定義一個失敗的初始化方法,該初始化方法通過在init關(guān)鍵字(init?)之后放置問號來創(chuàng)建適當(dāng)類型的可選實(shí)例任岸。 另外享潜,您可以定義一個失敗的初始化方法剑按,該初始化方法創(chuàng)建適當(dāng)類型的隱式展開的可選實(shí)例澜术。 為此鸟废,請?jiān)趇nit關(guān)鍵字(init!)后面放置一個感嘆號,而不是問號锣枝。
您可以從init?委托 init! 反之亦然撇叁,您可以重寫init? 用init! 反之亦然畦贸。 您也可以從init到init!進(jìn)行委派,盡管如果init!這樣做會觸發(fā)一個斷言趋厉。 初始化方法導(dǎo)致初始化失敗君账。
7 必需的初始化方法
在定義類初始化方法之前編寫required的修飾符,以指示該類的每個子類都必須實(shí)現(xiàn)該初始化方法:
class SomeClass {
required init() {
// initializer implementation goes here
}
}
您還必須在所需的初始化方法的每個子類實(shí)現(xiàn)之前編寫required修飾符椭蹄,以指示初始化方法要求適用于鏈中的其他子類绳矩。 重寫必需的指定初始值設(shè)定項(xiàng)時翼馆,您無需編寫override修飾符:
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
注意
如果可以通過繼承的初始化方法滿足要求应媚,則不必提供所需的初始化方法的顯式實(shí)現(xiàn)审姓。
8 用閉包和函數(shù)設(shè)置默認(rèn)屬性值
如果存儲的屬性的默認(rèn)值需要一些自定義或設(shè)置,則可以使用閉包或全局函數(shù)為該屬性提供自定義的默認(rèn)值扎筒。 每當(dāng)初始化屬性所屬類型的新實(shí)例時嗜桌,都會調(diào)用閉包或函數(shù)骨宠,并將其返回值分配為屬性的默認(rèn)值相满。
這些類型的閉包或函數(shù)通常會創(chuàng)建與屬性相同類型的臨時值立美,調(diào)整該值以表示所需的初始狀態(tài),然后返回該臨時值以用作屬性的默認(rèn)值碌更。
這是有關(guān)如何使用閉包提供默認(rèn)屬性值的框架概述:
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
請注意,封口的大括號后跟一對空括號嘿棘。 這告訴Swift立刻執(zhí)行關(guān)閉鸟妙。 如果省略這些括號圆仔,則嘗試將閉包本身分配給屬性蔫劣,而不是閉包的返回值脉幢。
注意
如果使用閉包來初始化屬性嫌松,請記住在執(zhí)行閉包時實(shí)例的其余部分尚未初始化萎羔。 這意味著您無法從閉包內(nèi)部訪問任何其他屬性值碳默,即使這些屬性具有默認(rèn)值也是如此嘱根。 您也不能使用隱式的self屬性该抒,也不能調(diào)用實(shí)例的任何方法凑保。
下面的示例定義了一個稱為Chessboard的結(jié)構(gòu)體,該結(jié)構(gòu)體為國際象棋的棋盤建模频伤。 國際象棋在8 x 8的棋盤上進(jìn)行游戲剂买,黑白方塊交替出現(xiàn)。
為了表示此游戲板婚肆,Chessboard結(jié)構(gòu)體具有一個稱為boardColors的單個屬性坐慰,該屬性是64個Bool值的數(shù)組结胀。 數(shù)組中的true值表示一個黑色正方形糟港,false值表示一個白色正方形。 數(shù)組中的第一項(xiàng)代表板上的左上角正方形速和,而數(shù)組中的最后一項(xiàng)代表板上的右下角正方形颠放。
boardColors數(shù)組使用閉包初始化以設(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)創(chuàng)建新的Chessboard實(shí)例時碰凶,都會執(zhí)行閉包欲低,并計(jì)算并返回boardColors的默認(rèn)值伸头。 上面的示例中的閉包計(jì)算并設(shè)置了一個臨時數(shù)組temporaryBoard恤磷,以為板上的每個正方形設(shè)置適當(dāng)?shù)念伾⒃谕瓿稍O(shè)置后將該臨時數(shù)組作為閉包的返回值返回野宜。 返回的數(shù)組值存儲在boardColors中扫步,可以使用squareIsBlackAt(row:column :)實(shí)用程序函數(shù)進(jìn)行查詢:
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"
總結(jié)
這一章節(jié)內(nèi)容有點(diǎn)冗長,翻譯得有點(diǎn)費(fèi)勁且不好理解匈子,Swift的初始化的方法理解就可以了河胎,有幾個點(diǎn)要小結(jié)一下:
- convenience關(guān)鍵詞的使用:定義便捷的初始化方法,在這個便捷初始化方法里虎敦,需要調(diào)用同一個類游岳、結(jié)構(gòu)體和枚舉里的另一個初始化方法。
- 初始化失敗的可選型:與聲明可選屬性的方式一樣喷户,在init后面加?(問好),表示這個初始化方法允許失敗河哑。
- 初始化失敗強(qiáng)制解包:表示這個初始化方法返回必須有值,用init!表示睬罗。
- required關(guān)鍵詞:修飾init方法,表示子類必須實(shí)現(xiàn)父類的init方法垂券。
主要記住這幾個點(diǎn),其他的內(nèi)容理解即可凳宙,文章內(nèi)容冗長,看的頭暈?zāi)X脹是尖,可以不用花太多時間。最后兜辞,善良的你肯定會鼓勵鼓勵我的吧逸吵,謝謝啦~
參考文檔: Swift - Initialization