- 過(guò)定義構(gòu)造器來(lái)實(shí)現(xiàn)構(gòu)造過(guò)程,它就像用來(lái)創(chuàng)建特定類型新實(shí)例的特殊方法。
- Swift 的構(gòu)造器沒(méi)有返回值。它們的主要任務(wù)是保證某種類型的新實(shí)例在第一次使用前完成正確的初始化。
1. 存儲(chǔ)屬性的初始賦值
類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。
-
存儲(chǔ)型屬性的值不能處于一個(gè)未知的狀態(tài)。
注意:當(dāng)你為存儲(chǔ)型屬性分配默認(rèn)值或者在構(gòu)造器中為設(shè)置初始值時(shí),它們的值是被直接設(shè)置的,不會(huì)觸發(fā)任何屬性觀察者。
1.1 構(gòu)造器
- 構(gòu)造器在創(chuàng)建某個(gè)特定類型的新實(shí)例時(shí)被調(diào)用。它的最簡(jiǎn)形式類似于一個(gè)不帶任何形參的實(shí)例方法,以關(guān)鍵字 init 命名:
init() {
// 在此處執(zhí)行構(gòu)造過(guò)程
}
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))。
1.2 默認(rèn)屬性值
- 可以在屬性聲明時(shí)為其設(shè)置默認(rèn)值。
- 可以通過(guò)在屬性聲明時(shí)為 temperature 提供默認(rèn)值來(lái)使用更簡(jiǎn)單的方式定義結(jié)構(gòu)體 Fahrenheit :
struct Fahrenheit {
var temperature = 32.0
}
注意
1. 如果一個(gè)屬性總是使用相同的初始值,那么為其設(shè)置一個(gè)默認(rèn)值比每次都在構(gòu)造器中賦值要好。
2. 兩種方法的最終結(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é)將講到。
2. 自定義構(gòu)造過(guò)程
- 可以通過(guò)輸入形參和可選屬性類型來(lái)自定義構(gòu)造過(guò)程。
- 也可以在構(gòu)造過(guò)程中分配常量屬性
2.1 形參的構(gòu)造過(guò)程
- 可以在定義中提供構(gòu)造形參,指定其值的類型和名字。構(gòu)造形參的功能和語(yǔ)法跟函數(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 中。
2.2 形參命名和實(shí)參標(biāo)簽
- 構(gòu)造形參可以同時(shí)使用在構(gòu)造器里使用的形參命名和一個(gè)外部調(diào)用構(gòu)造器時(shí)使用的實(shí)參標(biāo)簽。
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
}
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
let veryGreen = Color(0.0, 1.0, 0.0) // 報(bào)編譯期錯(cuò)誤-需要實(shí)參標(biāo)簽
注意,如果不通過(guò)實(shí)參標(biāo)簽傳值,這個(gè)構(gòu)造器是沒(méi)法調(diào)用的。如果構(gòu)造器定義了某個(gè)實(shí)參標(biāo)簽,就必須使用它,忽略它將導(dǎo)致編譯期錯(cuò)誤。
2.3 不帶實(shí)參標(biāo)簽的構(gòu)造器形參
- 如果你不希望構(gòu)造器的某個(gè)形參使用實(shí)參標(biāo)簽,可以使用下劃線(_)來(lái)代替顯式的實(shí)參標(biāo)簽來(lái)重寫默認(rèn)行為。
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)造器。
2.4 可選屬性類型
- 如果你自定義的類型有一個(gè)邏輯上允許值為空的存儲(chǔ)型屬性——無(wú)論是因?yàn)樗鼰o(wú)法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)機(jī)可以賦值為空——都需要將它聲明為 可選類型。
- 可選類型的屬性將自動(dòng)初始化為 nil,表示這個(gè)屬性是特意在構(gòu)造過(guò)程設(shè)置為空。
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)有字符“。
2.5 構(gòu)造過(guò)程中常量屬性的賦值
- 可以在構(gòu)造過(guò)程中的任意時(shí)間點(diǎn)給常量屬性賦值,只要在構(gòu)造過(guò)程結(jié)束時(shí)它設(shè)置成確定的值。
- 一旦常量屬性被賦值,它將永遠(yuǎn)不可更改。
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.)"
注意
對(duì)于類的實(shí)例來(lái)說(shuō),它的常量屬性只能在定義它的類的構(gòu)造過(guò)程中修改;
不能在子類中修改。
3. 默認(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í)例。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
上面例子中使用默認(rèn)構(gòu)造器創(chuàng)造了一個(gè) ShoppingListItem 類的實(shí)例(使用 ShoppingListItem() 形式的構(gòu)造器語(yǔ)法),并將其賦值給變量 item。(由于 name 屬性是可選 String 類型,它將接收一個(gè)默認(rèn) nil 的默認(rèn)值,盡管代碼中沒(méi)有寫出這個(gè)值)
3.1 結(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)造器。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
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"
4. 值類型的構(gòu)造器代理
- 通過(guò)調(diào)用其它構(gòu)造器來(lái)完成實(shí)例的部分構(gòu)造過(guò)程。這一過(guò)程稱為構(gòu)造器代理。
- 它能避免多個(gè)構(gòu)造器間的代碼重復(fù)。
- 值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過(guò)程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o自己的其它構(gòu)造器。
- 類則不同,它可以繼承自其它類。這意味著類有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化。
- 對(duì)于值類型,你可以使用 self.init 在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用 self.init。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
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)
}
}
let basicRect = Rect() // basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
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)
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è) 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í)例,它的 origin 和 size 屬性都使用定義時(shí)的默認(rèn)值 Point(x: 0.0, y: 0.0) 和 Size(width: 0.0, height: 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ǔ)型屬性。
- 第三個(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)的屬性中,構(gòu)造器 init(center:size:) 可以直接將 origin 和 size 的新值賦值到對(duì)應(yīng)的屬性中。然而,構(gòu)造器 init(center:size:) 通過(guò)使用提供了相關(guān)功能的現(xiàn)有構(gòu)造器將會(huì)更加便捷(而且意圖更清晰)。
5. 類的繼承和構(gòu)造過(guò)程
- 類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過(guò)程中設(shè)置初始值。
- Swift 為類類型提供了兩種構(gòu)造器來(lái)確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值:指定構(gòu)造器和便利構(gòu)造器。
5.1 指定構(gòu)造器和便利構(gòu)造器
- 指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并調(diào)用合適的父類構(gòu)造器讓構(gòu)造過(guò)程沿著父類鏈繼續(xù)往上進(jìn)行。
- 每一個(gè)類都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類通過(guò)繼承了父類中的指定構(gòu)造器而滿足了這個(gè)條件。
- 便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來(lái)調(diào)用同一個(gè)類中的指定構(gòu)造器,并為部分形參提供默認(rèn)值。你也可以定義便利構(gòu)造器來(lái)創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。
5.2 指定構(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
}
5.3 類類型的構(gòu)造器代理
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)造器必須總是橫向代理
5.4 兩段式構(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ǔ)型屬性。
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)用方法。
階段 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。
5.5 構(gòu)造器的繼承和重寫
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下被繼承。、
class Vehicle { // Vehicle 類只為存儲(chǔ)型屬性提供默認(rèn)值,也沒(méi)有提供自定義構(gòu)造器。因此,它會(huì)自動(dòng)獲得一個(gè)默認(rèn)構(gòu)造器
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)") // Vehicle: 0 wheel(s)
class Bicycle: Vehicle { // Bicycle 定義了一個(gè)自定義指定構(gòu)造器 init()。這個(gè)指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以 Bicycle 中這個(gè)版本的構(gòu)造器需要帶上 override 修飾符。
override init() {
super.init() // 這個(gè)方法的作用是調(diào)用 Bicycle 的父類 Vehicle 的默認(rèn)構(gòu)造器,可以確保 Bicycle 在修改屬性之前,它所繼承的屬性 numberOfWheels 能被 Vehicle 類初始化。
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)") // 打印“Bicycle: 2 wheel(s)”
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)"
}
}
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
注意:子類可以在構(gòu)造過(guò)程修改繼承來(lái)的變量屬性,但是不能修改繼承來(lái)的常量屬性。
5.6 構(gòu)造器的自動(dòng)繼承
- 規(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)造器。
5.7 指定構(gòu)造器和便利構(gòu)造器實(shí)踐
class Food { // 一個(gè)簡(jiǎn)單的用來(lái)封裝食物名字的類
var name: String
init(name: String) {
self.name = name
}
// RecipeIngredient 用來(lái)表示食譜中的一項(xiàng)原料
convenience init() { // 沒(méi)有參數(shù)的便利構(gòu)造器 init(),為新食物提供了一個(gè)默認(rèn)的占位名字,通過(guò)橫向代理到指定構(gòu)造器 init(name: String) 并給參數(shù) name 賦值為 [Unnamed] 來(lái)實(shí)現(xiàn)
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon") // 這個(gè)構(gòu)造器可以使用一個(gè)特定的名字來(lái)創(chuàng)建新的 Food 實(shí)例
// namedMeat 的名字是 "Bacon"
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
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)
}
}
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
// ShoppingListItem,這個(gè)類構(gòu)建了購(gòu)物單中出現(xiàn)的某一種食譜原料。
class ShoppingListItem: RecipeIngredient {
var purchased = false // 購(gòu)買狀態(tài)
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ?" : " ?"
return output
}
}
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 ?
6. 可失敗構(gòu)造器
有時(shí),定義一個(gè)構(gòu)造器可失敗的類,結(jié)構(gòu)體或者枚舉是很有用的。
這里所指的“失敗” 指的是,如給構(gòu)造器傳入無(wú)效的形參,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。
可以在一個(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語(yǔ)法為在 init 關(guān)鍵字后面添加問(wèn)號(hào)(init?)。
-
可失敗構(gòu)造器會(huì)創(chuàng)建一個(gè)類型為自身類型的可選類型的對(duì)象。你通過(guò) return nil 語(yǔ)句來(lái)表明可失敗構(gòu)造器在何種情況下應(yīng)該 “失敗”。
注意: 1. 可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其參數(shù)類型相同。 2. 嚴(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)造成功。
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”
實(shí)現(xiàn)針對(duì)數(shù)字類型轉(zhuǎn)換的可失敗構(gòu)造器。確保數(shù)字類型之間的轉(zhuǎn)換能保持精確的值,使用這個(gè) init(exactly:) 構(gòu)造器。如果類型轉(zhuǎn)換不能保持值不變,則這個(gè)構(gòu)造器構(gòu)造失敗。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { // 這個(gè)可失敗構(gòu)造器檢查傳入的species 值是否為一個(gè)空字符串。
return nil // 如果為空字符串,則構(gòu)造失敗。
}
self.species = species // 否則,species 屬性被賦值,構(gòu)造成功。
}
}
- 可以通過(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)造失?。?/li>
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”
注意:
1. 檢查空字符串的值(如 "",而不是 "Giraffe" )和檢查值為 nil 的可選類型的字符串是兩個(gè)完全不同的概念。
2. 上例中的空字符串("")其實(shí)是一個(gè)有效的,非可選類型的字符串。
3. 這里我們之所以讓 Animal 的可失敗構(gòu)造器構(gòu)造失敗,只是因?yàn)閷?duì)于 Animal 這個(gè)類的 species 屬性來(lái)說(shuō),它更適合有一個(gè)具體的值,而不是空字符串。
6.1 枚舉類型的可失敗構(gòu)造器
- 可以通過(guò)一個(gè)帶一個(gè)或多個(gè)形參的可失敗構(gòu)造器來(lái)獲取枚舉類型中特定的枚舉成員。
- 如果提供的形參無(wú)法匹配任何枚舉成員,則構(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.”
6.2 帶原始值的枚舉類型的可失敗構(gòu)造器
- 帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:)。
- 該可失敗構(gòu)造器有一個(gè)合適的原始值類型的 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.”
6.3 構(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ò)程中。
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product { // 這個(gè)類建立了一個(gè)在線購(gòu)物車中的物品的模型
let quantity: Int // 常量存儲(chǔ)型屬性,并確保該屬性的值至少為 1
init?(name: String, quantity: Int) {
if quantity < 1 { return nil } // CartItem 可失敗構(gòu)造器首先驗(yàn)證接收的 quantity 值是否大于等于 1, 倘若 quantity 值無(wú)效,則立即終止整個(gè)構(gòu)造過(guò)程,返回失敗結(jié)果,且不再執(zhí)行余下代碼。
self.quantity = quantity
super.init(name: name)
}
}
// 傳入一個(gè)非空字符串 name 以及一個(gè)值大于等于 1 的 quantity 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例
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)造器失敗
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ò)程失?。?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”
6.4 重寫一個(gè)可失敗構(gòu)造器
可以在子類中重寫父類的可失敗構(gòu)造器。
或者你也可以用子類的非可失敗構(gòu)造器重寫一個(gè)父類的可失敗構(gòu)造器。
-
這使你可以定義一個(gè)不會(huì)構(gòu)造失敗的子類,即使父類的構(gòu)造器允許構(gòu)造失敗。
注意: 1. 當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時(shí),向上代理到父類的可失敗構(gòu)造器的唯一方式是對(duì)父類的可失敗構(gòu)造器的返回值進(jìn)行強(qiáng)制解包。 2. 你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器,但反過(guò)來(lái)卻不行。
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
}
}
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) { // 用一個(gè)不可失敗構(gòu)造器 init(name:) 重寫了父類的可失敗構(gòu)造器 init?(name:),子類用一個(gè)不可失敗構(gòu)造器代替了父類的可失敗構(gòu)造器。
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
class UntitledDocument: Document {
override init() { // name 屬性的值總是 "[Untitled]",它在構(gòu)造過(guò)程中使用了父類的可失敗構(gòu)造器 init?(name:)
super.init(name: "[Untitled]")!
}
- 如果在調(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ò)誤。
6.5 init! 可失敗構(gòu)造器
- init 關(guān)鍵字后添加問(wèn)號(hào)的方式(init?)來(lái)定義一個(gè)可失敗構(gòu)造器。
- 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è)斷言。
7. 必要構(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)。
8. 通過(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)于將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。
注意
1. 如果你使用閉包來(lái)初始化屬性,請(qǐng)記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒(méi)有初始化。
2. 這意味著你不能在閉包里訪問(wèn)其它屬性,即使這些屬性有默認(rèn)值。
3. 同樣,你也不能使用隱式的 self 屬性,或者調(diào)用任何實(shí)例方法。
struct Chessboard {
/*
boardColors:一個(gè)包含 64 個(gè) Bool 值的數(shù)組
值為 true 的元素表示一個(gè)黑格
值為 false 的元素表示一個(gè)白格
數(shù)組中第一個(gè)元素代表棋盤上左上角的格子
最后一個(gè)元素代表棋盤上右下角的格子。
*/
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]
}
}
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”