本頁包含內(nèi)容:
[TOC]
類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值。
Swift為類類型提供了兩種構(gòu)造器來確保實(shí)例中所有存儲型屬性都能獲得初始值,分別是:
- 指定構(gòu)造器
- 便利構(gòu)造器
指定構(gòu)造器和便利構(gòu)造器
指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類合適的構(gòu)造器來實(shí)現(xiàn)父類的初始化。
類傾向于擁有少量指定構(gòu)造器,普遍的是一個(gè)類擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器在初始化的地方通過“管道”將初始化過程持續(xù)到父類鏈。
每一個(gè)類都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類通過繼承父類中的指定構(gòu)造器而滿足了這個(gè)條件。
遍歷構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用一個(gè)類中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。你也可以定義便利構(gòu)造器來創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。
你應(yīng)當(dāng)只在必要的時(shí)候?yàn)轭愄峁┍憷麡?gòu)造器,比方說某種情況下通過使用便利構(gòu)造器來快捷調(diào)用某個(gè)指定構(gòu)造器,能夠節(jié)省更多開發(fā)時(shí)間并讓類的構(gòu)造過程更加清晰明了。
指定構(gòu)造器和便利構(gòu)造器的語法
類的指定構(gòu)造器寫法跟值類型簡單構(gòu)造器一樣:
init(parameters) {
// statements
}
便利構(gòu)造器也采用相同樣式的寫法,但需要在init
關(guān)鍵字之前放置convenience
關(guān)鍵字,并使用空格將它們倆分開:
convenience init(parameters) {
// statements
}
類的構(gòu)造器代理規(guī)則
為了簡化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,Swift采用了一下三條規(guī)則類限制構(gòu)造器之間的代理調(diào)用:
- 指定構(gòu)造器必須調(diào)用其直接父類的指定構(gòu)造器。
- 便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器。
- 便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。
一個(gè)更方便的記憶方法是:
- 指定構(gòu)造器必須總是向上代理。
- 便利構(gòu)造器必須總是橫向代理。
這些規(guī)則可以通過下面的圖例來說明:
[圖片上傳失敗...(image-4e3b82-1520508470408)]
如圖所示,父類中包含一個(gè)指定構(gòu)造器和兩個(gè)便利構(gòu)造器。其中一個(gè)便利構(gòu)造器調(diào)用了另外一個(gè)便利構(gòu)造器,而后者又調(diào)用了唯一的指定構(gòu)造器。這滿足了上面提到的規(guī)則 2 和 3。這個(gè)父類沒有自己的父類,所以規(guī)則 1 沒有用到。
子類中包含兩個(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。
下面圖例中展示了一種涉及四個(gè)類的更復(fù)雜的類層級結(jié)構(gòu)。它演示了指定構(gòu)造器是如何在類層級中充當(dāng)“管道”的作用,在類的構(gòu)造器鏈上簡化了類之間的相互關(guān)系。
[圖片上傳失敗...(image-2ff209-1520508470408)]
兩段式構(gòu)造過程
Swift中類的構(gòu)造過程包含兩個(gè)階段:
- 第一個(gè)階段,類中的每個(gè)存儲型屬性賦一個(gè)初始值。當(dāng)每個(gè)存儲型屬性的初始值被賦值后,第二階段開始。
- 第二階段,它給每個(gè)類一次機(jī)會,在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲型屬性。
兩段式構(gòu)造過程的使用讓構(gòu)造過程更安全,同時(shí)在整個(gè)類層級結(jié)構(gòu)中給予了每個(gè)類完全的靈活性。兩段式構(gòu)造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個(gè)構(gòu)造器意外地賦予不同的值。
注意:Swift 的兩段式構(gòu)造過程跟 Objective-C 中的構(gòu)造過程類似。最主要的區(qū)別在于階段 1,Objective-C 給每一個(gè)屬性賦值
0
或空值(比如說0
或nil
)。Swift 的構(gòu)造流程則更加靈活,它允許你設(shè)置定制的初始值,并自如應(yīng)對某些屬性不能以0
或nil
作為合法默認(rèn)值的情況。
4種安全檢查
Swift編譯器將執(zhí)行4種有效的安全檢查,以確保兩段式構(gòu)造過程不出錯(cuò)地完成:
- 指定構(gòu)造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器。一個(gè)對象的內(nèi)存只有在其所有存儲型屬性確定之后才能完全初始化。為了滿足這一規(guī)則,指定構(gòu)造器必須保證它所在類的屬性在它往上代理之前先完成初始化。
- 指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類構(gòu)造器,如果沒有這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。
- 便利構(gòu)造器必須為任意屬性賦新值之前代理調(diào)用同一類中的其他構(gòu)造器,如果沒有這么做,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋。
- 構(gòu)造器在第一階段完成之前,不能調(diào)用任何實(shí)例方法,不能讀取任何實(shí)例屬性的值,不能引用
self
作為一個(gè)值。類實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有在第一階段完成后,該實(shí)例才會成為有效實(shí)例,才能訪問屬性和調(diào)用方法。
以下是兩段式構(gòu)造過程中基于上述安全檢查的構(gòu)造流程展示:
階段1:
- 某個(gè)指定構(gòu)造器或遍歷構(gòu)造器被調(diào)用。
- 完成新實(shí)例內(nèi)存的分配,但此時(shí)內(nèi)存還沒有被初始化。
- 指定構(gòu)造器確保其所在類引入的所有存儲型屬性都已經(jīng)賦值,存儲型屬性所屬的內(nèi)存完成初始化。
- 指定構(gòu)造器將調(diào)用父類的構(gòu)造器,完成父類屬性的初始化。
- 這個(gè)調(diào)用父類構(gòu)造器的過程沿著構(gòu)造器鏈一直往上執(zhí)行,直到到達(dá)構(gòu)造器鏈的最頂部。
- 當(dāng)?shù)竭_(dá)構(gòu)造器鏈的最頂部,且已確保所有實(shí)例包含的存儲型屬性都已經(jīng)賦值,這個(gè)實(shí)例的內(nèi)存被認(rèn)為已經(jīng)完全初始化。此時(shí)階段1完成。
階段2:
- 從頂部構(gòu)造器鏈一直往下,每個(gè)構(gòu)造器鏈中類的指定構(gòu)造器都有機(jī)會進(jìn)一步定制實(shí)例。構(gòu)造器此時(shí)可以訪問
self
、修改它的屬性并調(diào)用實(shí)例方法等等。 - 最終,任意構(gòu)造器鏈中的遍歷構(gòu)造器可以有機(jī)會定制實(shí)例和使用
self
。
下圖展示了在假定的子類和父類之間的構(gòu)造階段1:
[圖片上傳失敗...(image-8cb387-1520508470408)]
在這個(gè)例子中,構(gòu)造過程從對子類中一個(gè)便利構(gòu)造器的調(diào)用開始。這個(gè)便利構(gòu)造器此時(shí)沒法修改任何屬性,它把構(gòu)造任務(wù)代理給同一類中的指定構(gòu)造器。
如安全檢查1所示,指定構(gòu)造器將確保所有子類的屬性都有值。然后它將調(diào)用父類的指定構(gòu)造器,并沿著構(gòu)造器鏈一直往上完成父類的構(gòu)造過程。
父類中的指定構(gòu)造器確保所有父類的屬性都有值。由于沒有更多的父類需要初始化,也就無需繼續(xù)向上代理。
一旦父類中所有屬性都有了初始值,實(shí)例的內(nèi)存被認(rèn)為是完全初始化,階段1完成。
以下展示了相同構(gòu)造過程的階段2:
[圖片上傳失敗...(image-529f06-1520508470408)]
父類中的指定構(gòu)造器現(xiàn)在有機(jī)會進(jìn)一步來定制實(shí)例(盡管這不是必須的)。
一旦父類中的指定構(gòu)造器完成調(diào)用,子類中的指定構(gòu)造器可以執(zhí)行更多的定制操作(這也不是必須的)。
最終,一旦子類的指定構(gòu)造器完成調(diào)用,最開始被調(diào)用的遍歷構(gòu)造器可以執(zhí)行更多的定制操作。
構(gòu)造器的繼承和重寫
跟Objective-C中的子類不同,Swift中的子類默認(rèn)情況下不會繼承父類的構(gòu)造器。Swift的這種機(jī)制可以防止一個(gè)父類的簡單構(gòu)造器被一個(gè)更精細(xì)的子類繼承,并被錯(cuò)誤地用來創(chuàng)建子類的實(shí)例。
假如你希望自定義的子類中能夠提供一個(gè)或多個(gè)跟父類相同的構(gòu)造器,你可以在子類中提供這些構(gòu)造器的自定義實(shí)現(xiàn)。
當(dāng)你在編寫一個(gè)和父類中指定構(gòu)造器時(shí),你實(shí)際上是在重寫父類的這個(gè)指定構(gòu)造器。因此,你必須在定義子類構(gòu)造器時(shí)帶上override
修飾符。即使你重寫的是系統(tǒng)自動提供的默認(rèn)構(gòu)造器,也需要帶上override
修飾符。
相反,如果你編寫了一個(gè)和父類遍歷構(gòu)造器相匹配的子類構(gòu)造器,由于子類不能直接調(diào)用父類的便利構(gòu)造器,因此,嚴(yán)格意義上來講,你的子類并未對一個(gè)父類構(gòu)造器提供重寫。最后的結(jié)果就是,你在子類“重寫”一個(gè)父類便利構(gòu)造器時(shí),不需要加override
修飾符。
在下面的例子中定義了一個(gè)叫 Vehicle
的基類。基類中聲明了一個(gè)存儲型屬性 numberOfWheels
,它是默認(rèn)值為 0
的 Int
類型的存儲型屬性。numberOfWheels
屬性用于創(chuàng)建名為 descrpiption
的 String
類型的計(jì)算型屬性:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle
類只為存儲型屬性提供默認(rèn)值,也沒有提供自定義構(gòu)造器。因此,它會自動獲得一個(gè)默認(rèn)構(gòu)造器。自動獲得的默認(rèn)構(gòu)造器總是類中的指定構(gòu)造器,它可以用于創(chuàng)建numberOfWheels
為 0
的 Vehicle
實(shí)例:
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
下面的例子中定義了一個(gè)Vehicle
的子類Bicycle
:
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
子類Bicycle
定義了一個(gè)自定義指定構(gòu)造器init()
。這個(gè)指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以Bicycle
中的指定構(gòu)造器需要帶上override
修飾符。
Bicycle
的構(gòu)造器init()
以調(diào) super.init()
方法開始,這個(gè)方法的作用是調(diào)用Bicycle
的父類Vehicle
的默認(rèn)構(gòu)造器。這樣可以確保Bicycle
在修改屬性之前,它所繼承的屬性numberOfWheels
能被Vehicle
類初始化。在調(diào)用super.init()
之后,屬性numberOfWheels
的原值被新值 2
替換。
如果你創(chuàng)建了一個(gè)Bicycle
實(shí)例,你可以調(diào)用繼承的description
計(jì)算型屬性去查看屬性numberOfWheels
是否有改變:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"
注意:子類可以在初始化時(shí)修改繼承來的變量屬性,但是不能修改繼承來的常量屬性。
構(gòu)造器的自動繼承
如上所述,子類在默認(rèn)情況下不會繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動繼承的。事實(shí)上,這意味著對于許多常見場景你不必重寫父類的構(gòu)造器,并且可以在安全的情況下以最小的代價(jià)繼承父類的構(gòu)造器。
假設(shè)你為子類中引入的所有新屬性都提供了默認(rèn)值,一下2個(gè)規(guī)則適用:
- 如果子類沒有定義任何指定構(gòu)造器,它將自動繼承父類所有的指定構(gòu)造器。
- 如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn) - - 無論是通過規(guī)則1繼承過來的,還是提供了自定義實(shí)現(xiàn) - - 它將自動繼承父類所有的便利構(gòu)造器。
即使你在子類中添加了更多的遍歷構(gòu)造器,這兩條規(guī)則任然適用。
注意:對于規(guī)則2,子類可以將父類的指定構(gòu)造器實(shí)現(xiàn)為便利構(gòu)造器。
指定構(gòu)造器和便利構(gòu)造器實(shí)踐
接下來的例子將在實(shí)踐中展示指定構(gòu)造器、遍歷構(gòu)造器以及構(gòu)造器的自動繼承。
這個(gè)例子定義了包含三個(gè)類:Food
、RecipeIngredient
以及ShoppingListItem
的類層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。
類層次中的基類是Food
,它是一個(gè)簡單的用來封裝食物名字的類。Food
類引入了一個(gè)叫做name
的String
類型的屬性,并且提供了兩個(gè)構(gòu)造器來創(chuàng)建Food
實(shí)例:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
下圖中展示了Food
的構(gòu)造器鏈:
[圖片上傳失敗...(image-c81f51-1520508470408)]
類類型沒有默認(rèn)的逐一成員構(gòu)造器,所以Food
類提供了一個(gè)接受單一參數(shù)name
的指定構(gòu)造器。這個(gè)構(gòu)造器可以使用一個(gè)特定的名字來創(chuàng)建新的Food
實(shí)例:
let namedMeat = Food(name: "Bacon")
// namedMeat的名字是"Bacon"
Food
類中的構(gòu)造器init(name: String)
被定義為一個(gè)指定構(gòu)造器,因?yàn)樗艽_保Food
實(shí)例的所有存儲屬性都被初始化。Food
類沒有父類,所有init(name: String)
構(gòu)造器不需要調(diào)用super.init()
來完成構(gòu)造過程。
Food
類同樣提供一個(gè)沒有參數(shù)的便利構(gòu)造器init()
。這個(gè)init()
構(gòu)造器為新食物提供了一個(gè)默認(rèn)的占位名字,通過橫向代理到指定構(gòu)造器init(name: String)
并給name
賦值為[Unnamed]
來實(shí)現(xiàn):
let mySteryMeat = Food()
// mySteryMeat的名字是"[Unnamed]"
類層級中的第二個(gè)類是Food
的子類RecipeIngredient
。RecipeIngredient
類用來表示食譜中的一項(xiàng)原料。它引入了Int
類型的屬性quantity
,并且定義了兩個(gè)構(gòu)造器來創(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)造鏈:
[圖片上傳失敗...(image-a5036a-1520508470408)]
RecipeIngredient
類擁有一個(gè)指定構(gòu)造器init(name: String, quantity: Int)
,它可以用來填充RecipeIngredient
實(shí)例的所有屬性值。這個(gè)構(gòu)造器一開始先將傳入的quantity
參數(shù)賦值給quantity
屬性,這個(gè)屬性也是唯一在RecipeIngredient
中新引入的屬性。隨后,構(gòu)造器向上代理到父類Fodd
的init(name: String)
。這個(gè)過程滿足兩段式構(gòu)造中的安全檢查1。
RecipeIngredient
也定義了一個(gè)便利構(gòu)造器init(name: String)
,它只通過name
來創(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)造器只是簡單地橫向代理到類中的指定構(gòu)造器,并為quantity
參數(shù)傳遞1
。
注意:RecipeIngredient
的遍歷構(gòu)造器init(name: String)
使用了跟Food
中指定構(gòu)造器init(name: String)
相同的參數(shù)。由于這個(gè)便利構(gòu)造器重寫了父類的指定構(gòu)造器init(name: String)
,因此必須在前面使用override
修飾符。
盡管RecipeIngredient
將父類的指定構(gòu)造器重寫為了便利構(gòu)造器,但是它依然提供了父類的所有指定構(gòu)造器的實(shí)現(xiàn)。因此,RecipeIngredient
會自動繼承父類的所有便利構(gòu)造器。
在這個(gè)例子中,RecipeIngredient
的父類是Food
,它有一個(gè)便利構(gòu)造器init()
。這個(gè)便利構(gòu)造器會被RecipeIngredient
繼承。這個(gè)繼承版本的init()
在功能上跟Food
提供的版本是一樣的,只是它會代理到RecipeIngredient
版本的init(name: String)
而不是Food
提供的版本。
所有的三種構(gòu)造器都可以用來創(chuàng)建新的RecipeIngredient
實(shí)例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
類層級中第三個(gè)也是最后一個(gè)類是RecipeIngredient
的子類,叫做ShoppingListItem
。這個(gè)類構(gòu)建了購物單中出現(xiàn)的某一種食譜原料。
購物單中的每一項(xiàng)總是從未購買狀態(tà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
沒有定義構(gòu)造器來為purchased
提供初始值,因?yàn)樘砑拥劫徫飭蔚奈锲返某跏紶顟B(tài)總是未購買。
由于它為自己引入的所有屬性都提供了默認(rèn)值,并且自己沒有定義任何構(gòu)造器,ShoppingListItem
將自動繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器。
下圖展示了這三個(gè)類的構(gòu)造器鏈:
[圖片上傳失敗...(image-faf8ca-1520508470408)]
你可以使用三個(gè)繼承來的構(gòu)造器來創(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 ?
如上所述,例子中通過字面量方式創(chuàng)建了一個(gè)數(shù)組 breakfastList
,它包含了三個(gè) ShoppingListItem
實(shí)例,因此數(shù)組的類型也能被自動推導(dǎo)為 [ShoppingListItem]
。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個(gè) ShoppingListItem
實(shí)例的名字從 [Unnamed]
更改為 Orange juice
,并標(biāo)記狀態(tài)為已購買。打印數(shù)組中每個(gè)元素的描述顯示了它們都已按照預(yù)期被賦值。
可失敗構(gòu)造器
如果一個(gè)類、結(jié)構(gòu)體或枚舉類型的對象,在構(gòu)造過程中有可能失敗,則為其定義一個(gè)可失敗的構(gòu)造器是很有用的。這里所指的“失敗”指的使,如給構(gòu)造器傳入無效的參數(shù)值,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。
為了妥善處理這種構(gòu)造過程中可能會失敗的情況。你可以在一個(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語法為在init
關(guān)鍵字后面添加問號(init?
)。
注意:可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其參數(shù)類型相同。
可失敗構(gòu)造器會創(chuàng)建一個(gè)類型為自身類型的可選類型的對象。你通過return nil
語句來表明可失敗構(gòu)造器在何種情況下應(yīng)該“失敗”。
注意:嚴(yán)格來說,構(gòu)造器都不支持返回值。因?yàn)闃?gòu)造器本身的作用,只是為了確保對象能被正確構(gòu)造。因此你只是用
return nil
表明可失敗構(gòu)造器構(gòu)造失敗,而不要用關(guān)鍵字return
來表明構(gòu)造成功。
例如,實(shí)現(xiàn)針對數(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è)名為species
的String
類型的常量屬性。同時(shí)該結(jié)構(gòu)體還定義了一個(gè)接受一個(gè)名為species
的String
類型參數(shù)的可失敗構(gòu)造器。這個(gè)可失敗的構(gòu)造器檢查傳入的參數(shù)是否為一個(gè)空字符串。如果為空字符串,則構(gòu)造失敗、否則,sepcied
屬性被賦值,構(gòu)造成功。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
你可以通過該可失敗構(gòu)造器來嘗試構(gòu)建一個(gè) Animal
的實(shí)例,并檢查構(gòu)造過程是否成功:
let someCreature = Animal(species: "Giraffe")
// someCreature 的類型是 Animal? 而不是 Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
如果你給該可失敗構(gòu)造器傳入一個(gè)空字符串作為其參數(shù),則會導(dǎo)致構(gòu)造失敗:
let anonymousCreature = Animal(species: "")
// anonymousCreature 的類型是 Animal?, 而不是 Animal
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
枚舉類型的可失敗構(gòu)造器
你可以通過一個(gè)帶一個(gè)或多個(gè)參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。如果提供的參數(shù)無法匹配任何枚舉成員,則構(gòu)造失敗。
下例中,定義了一個(gè)名為 TemperatureUnit
的枚舉類型。其中包含了三個(gè)可能的枚舉成員(Kelvin
,Celsius
,和Fahrenheit
),以及一個(gè)根據(jù) Character
值找出所對應(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)造失敗:
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)造器
帶原始值的枚舉類型會自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:)
,該可失敗構(gòu)造器有一個(gè)名為 rawValue
的參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個(gè)枚舉成員的原始值匹配,則該構(gòu)造器會構(gòu)造相應(yīng)的枚舉成員,否則構(gòu)造失敗。
因此上面的 TemperatureUnit
的例子可以重寫為:
enum TemperatureUnit: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
構(gòu)造失敗的傳遞
類、結(jié)構(gòu)體和枚舉的可失敗構(gòu)造器可以橫向代理到同類型中的其他可失敗構(gòu)造器。類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器。
無論是向上代理還是橫向代理,如果你代理到其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個(gè)構(gòu)造過程將立即終止,接下來的任何構(gòu)造代碼不會再被執(zhí)行。
下面這個(gè)例子,定義了一個(gè)名為CartItem
的Product
類的子類。這個(gè)類建立了一個(gè)在線購物車中的物品的模型,它有一個(gè)名為quantity
的常量存儲型屬性,并確保該屬性的值至少為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
值無效,則立即終止整個(gè)構(gòu)造過程,返回失敗結(jié)果,且不再執(zhí)行余下代碼。同樣地,Product
的可失敗構(gòu)造器首先檢查 name
值,假如 name
值為空字符串,則構(gòu)造器立即執(zhí)行失敗。
如果你通過傳入一個(gè)非空字符串 name
以及一個(gè)值大于等于 1 的 quantity
來創(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
來創(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
來創(chuàng)建一個(gè) CartItem
實(shí)例,那么將導(dǎo)致父類 Product
的構(gòu)造過程失敗:
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è)不會構(gòu)造失敗的子類,即使父類的構(gòu)造器允許構(gòu)造失敗。
注意:當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器是,向上代理到父類的可失敗構(gòu)造器的唯一方式是對父類的可失敗構(gòu)造器的返回值進(jìn)行強(qiáng)制解包。
注意:你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器,但反過來卻不行。
下例定義了一個(gè)名為 Document
的類,name
屬性的值必須為一個(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è)指定構(gòu)造器,確保了無論是使用 init()
構(gòu)造器,還是使用 init(name:)
構(gòu)造器并為參數(shù)傳遞空字符串,生成的實(shí)例中的 name
屬性總有初始"[Untitled]"
:
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.init()
} 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)制解包來調(diào)用父類的可失敗構(gòu)造器。比如下面的UntitledDocument
子類的name屬性值總是"[Untitled]"
,它在構(gòu)造過程中使用了父類的可失敗構(gòu)造器init?(name:)
:
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在這個(gè)例子中,如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:)
時(shí)傳入的是空字符串,那么強(qiáng)制解包操作會引發(fā)運(yùn)行時(shí)錯(cuò)誤。不過,因?yàn)檫@里是通過非空的字符串常量來調(diào)用它,所以并不會發(fā)生運(yùn)行時(shí)錯(cuò)誤。
可失敗構(gòu)造器 init!
通常來說我們通過在init
關(guān)鍵字后添加問號的方式init?
來定義一個(gè)可失敗構(gòu)造器,但你也可以通過在init
后面添加驚嘆號的方式來定義一個(gè)可失敗構(gòu)造器init!
,該可失敗構(gòu)造器將會創(chuàng)建一個(gè)對應(yīng)類型的隱式解包可選類型的對象。
你可以在init?
中代理到init!
,反之亦然。你也可以用init?
重寫init!
,反之亦然。你還可以用init
代理到init!
,不過一旦init!
構(gòu)造失敗,將會觸發(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)代碼
}
}
通過閉包或函數(shù)設(shè)置屬性的默認(rèn)值
如果某個(gè)存儲型屬性的默認(rèn)值需要一些定制或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值。每當(dāng)某個(gè)屬性所在類型的新實(shí)例被創(chuàng)建時(shí),對應(yīng)的閉包或函數(shù)會被調(diào)用,而它們的返回值會當(dāng)做默認(rèn)值賦給這個(gè)屬性。
這種類型的閉包或函數(shù)通常會創(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é)尾的花括號后面接了一對空的小括號。這用來告訴Swift立即執(zhí)行此閉包。如果你忽略了這對括號,相當(dāng)于將閉包本身作為值賦給了屬性,而不是將閉包的返回值賦值給屬性。
注意:如果你使用閉包來初始化屬性,請記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒有初始化。這意味著你不能再閉包里訪問其它屬性,即使這些屬性有默認(rèn)值。同樣,你也不能使用隱式的
self
屬性,或者調(diào)用任何實(shí)例方法。
下面例子中定義了一個(gè)結(jié)構(gòu)體 Chessboard
,它構(gòu)建了西洋跳棋游戲的棋盤,西洋跳棋游戲在一副黑白格交替的 8 x 8 的棋盤中進(jìn)行的:
[圖片上傳失敗...(image-5e833d-1520508470408)]
為了呈現(xiàn)這副游戲棋盤,Chessboard
結(jié)構(gòu)體定義了一個(gè)屬性 boardColors
,它是一個(gè)包含 64
個(gè) Bool
值的數(shù)組。在數(shù)組中,值為 true
的元素表示一個(gè)黑格,值為 false
的元素表示一個(gè)白格。數(shù)組中第一個(gè)元素代表棋盤上左上角的格子,最后一個(gè)元素代表棋盤上右下角的格子。
boardColors
數(shù)組是通過一個(gè)閉包來初始化并設(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í),賦值閉包則會被執(zhí)行,boardColors
的默認(rèn)值會被計(jì)算出來并返回。上面例子中描述的閉包將計(jì)算出棋盤中每個(gè)格子對應(yīng)的顏色,并將這些值保存到一個(gè)臨時(shí)數(shù)組 temporaryBoard
中,最后在構(gòu)建完成時(shí)將此數(shù)組作為閉包返回值返回。這個(gè)返回的數(shù)組會保存到 boardColors
中,并可以通過工具函數(shù)squareIsBlackAtRow
來查詢:
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false”