構(gòu)造過程是為了使用某個(gè)類、結(jié)構(gòu)體或枚舉類型的實(shí)例而進(jìn)行的準(zhǔn)備過程。這個(gè)過程包含了為實(shí)例中的每個(gè)屬性設(shè)置初始值和為其執(zhí)行必要的準(zhǔn)備和初始化任務(wù)。
構(gòu)造過程是通過定義構(gòu)造器(Initializers
)來實(shí)現(xiàn)的,這些構(gòu)造器可以看做是用來創(chuàng)建特定類型實(shí)例的特殊方法。與 Objective-C 中的構(gòu)造器不同,Swift 的構(gòu)造器無需返回值,它們的主要任務(wù)是保證新實(shí)例在第一次使用前完成正確的初始化。
類實(shí)例也可以通過定義析構(gòu)器(deinitializer
)在類實(shí)例釋放之前執(zhí)行特定的清除工作。想了解更多關(guān)于析構(gòu)器的內(nèi)容,請(qǐng)參考析構(gòu)過程。
類和結(jié)構(gòu)體在實(shí)例創(chuàng)建時(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ā)任何屬性觀測(cè)器(property observers
)。
構(gòu)造器在創(chuàng)建某特定類型的新實(shí)例時(shí)調(diào)用。它的最簡(jiǎn)形式類似于一個(gè)不帶任何參數(shù)的實(shí)例方法,以關(guān)鍵字init
命名。
下面例子中定義了一個(gè)用來保存華氏溫度的結(jié)構(gòu)體Fahrenheit
,它擁有一個(gè)Double
類型的存儲(chǔ)型屬性temperature
:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
println("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è)屬性總是使用同一個(gè)初始值,可以為其設(shè)置一個(gè)默認(rèn)值。無論定義默認(rèn)值還是在構(gòu)造器中賦值,最終它們實(shí)現(xiàn)的效果是一樣的,只不過默認(rèn)值將屬性的初始化和屬性的聲明結(jié)合的更緊密。使用默認(rèn)值能讓你的構(gòu)造器更簡(jiǎn)潔、更清晰,且能通過默認(rèn)值自動(dòng)推導(dǎo)出屬性的類型;同時(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
}
你可以通過輸入?yún)?shù)和可選屬性類型來定制構(gòu)造過程,也可以在構(gòu)造過程中修改常量屬性。這些都將在后面章節(jié)中提到。
你可以在定義構(gòu)造器時(shí)提供構(gòu)造參數(shù),為其提供定制化構(gòu)造所需值的類型和名字。構(gòu)造器參數(shù)的功能和語法跟函數(shù)和方法參數(shù)相同。
下面例子中定義了一個(gè)包含攝氏度溫度的結(jié)構(gòu)體Celsius
。它定義了兩個(gè)不同的構(gòu)造器:init(fromFahrenheit:)
和init(fromKelvin:)
,二者分別通過接受不同刻度表示的溫度值來創(chuàng)建新的實(shí)例:
struct Celsius {
var temperatureInCelsius: Double = 0.0
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ù)名字。
然而,構(gòu)造器并不像函數(shù)和方法那樣在括號(hào)前有一個(gè)可辨別的名字。所以在調(diào)用構(gòu)造器時(shí),主要通過構(gòu)造器中的參數(shù)名和類型來確定需要調(diào)用的構(gòu)造器。正因?yàn)閰?shù)如此重要,如果你在定義構(gòu)造器時(shí)沒有提供參數(shù)的外部名字,Swift 會(huì)為每個(gè)構(gòu)造器的參數(shù)自動(dòng)生成一個(gè)跟內(nèi)部名字相同的外部名,就相當(dāng)于在每個(gè)構(gòu)造參數(shù)之前加了一個(gè)哈希符號(hào)。
注意:
如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供外部名字,你可以使用下劃線_
來顯示描述它的外部名,以此覆蓋上面所說的默認(rèn)行為。
以下例子中定義了一個(gè)結(jié)構(gòu)體Color
,它包含了三個(gè)常量:red
、green
和blue
。這些屬性可以存儲(chǔ)0.0到1.0之間的值,用來指示顏色中紅、綠、藍(lán)成分的含量。
Color
提供了一個(gè)構(gòu)造器,其中包含三個(gè)Double
類型的構(gòu)造參數(shù):
struct Color {
let red = 0.0, green = 0.0, blue = 0.0
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
每當(dāng)你創(chuàng)建一個(gè)新的Color
實(shí)例,你都需要通過三種顏色的外部參數(shù)名來傳值,并調(diào)用構(gòu)造器。
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
注意,如果不通過外部參數(shù)名字傳值,你是沒法調(diào)用這個(gè)構(gòu)造器的。只要構(gòu)造器定義了某個(gè)外部參數(shù)名,你就必須使用它,忽略它將導(dǎo)致編譯錯(cuò)誤:
let veryGreen = Color(0.0, 1.0, 0.0)
// 報(bào)編譯時(shí)錯(cuò)誤,需要外部名稱
如果你定制的類型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性--不管是因?yàn)樗鼰o法在初始化時(shí)賦值,還是因?yàn)樗梢栽谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空--你都需要將它定義為可選類型optional type
??蛇x類型的屬性將自動(dòng)初始化為空nil
,表示這個(gè)屬性是故意在初始化時(shí)設(shè)置為空的。
下面例子中定義了類SurveyQuestion
,它包含一個(gè)可選字符串屬性response
:
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 輸出 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
調(diào)查問題在問題提出之后,我們才能得到回答。所以我們將屬性回答response
聲明為String?
類型,或者說是可選字符串類型optional String
。當(dāng)SurveyQuestion
實(shí)例化時(shí),它將自動(dòng)賦值為空nil
,表明暫時(shí)還不存在此字符串。
只要在構(gòu)造過程結(jié)束前常量的值能確定,你可以在構(gòu)造過程中的任意時(shí)間點(diǎn)修改常量屬性的值。
注意:
對(duì)某個(gè)類實(shí)例來說,它的常量屬性只能在定義它的類的構(gòu)造過程中修改;不能在子類中修改。
你可以修改上面的SurveyQuestion
示例,用常量屬性替代變量屬性text
,指明問題內(nèi)容text
在其創(chuàng)建之后不會(huì)再被修改。盡管text
屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽谄漕惖臉?gòu)造器中設(shè)置它的值:
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 輸出 "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
Swift 將為所有屬性已提供默認(rèn)值的且自身沒有定義任何構(gòu)造器的結(jié)構(gòu)體或基類,提供一個(gè)默認(rèn)的構(gòu)造器。這個(gè)默認(rèn)構(gòu)造器將簡(jiǎn)單的創(chuàng)建一個(gè)所有屬性值都設(shè)置為默認(rèn)值的實(shí)例。
下面例子中創(chuàng)建了一個(gè)類ShoppingListItem
,它封裝了購物清單中的某一項(xiàng)的屬性:名字(name
)、數(shù)量(quantity
)和購買狀態(tài) purchase state
。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于ShoppingListItem
類中的所有屬性都有默認(rèn)值,且它是沒有父類的基類,它將自動(dòng)獲得一個(gè)可以為所有屬性設(shè)置默認(rèn)值的默認(rèn)構(gòu)造器(盡管代碼中沒有顯式為name
屬性設(shè)置默認(rèn)值,但由于name
是可選字符串類型,它將默認(rèn)設(shè)置為nil
)。上面例子中使用默認(rèn)構(gòu)造器創(chuàng)造了一個(gè)ShoppingListItem
類的實(shí)例(使用ShoppingListItem()
形式的構(gòu)造器語法),并將其賦值給變量item
。
除上面提到的默認(rèn)構(gòu)造器,如果結(jié)構(gòu)體對(duì)所有存儲(chǔ)型屬性提供了默認(rèn)值且自身沒有提供定制的構(gòu)造器,它們能自動(dòng)獲得一個(gè)逐一成員構(gòu)造器。
逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法。我們?cè)谡{(diào)用逐一成員構(gòu)造器時(shí),通過與成員屬性名相同的參數(shù)名進(jìn)行傳值來完成對(duì)成員屬性的初始賦值。
下面例子中定義了一個(gè)結(jié)構(gòu)體Size
,它包含兩個(gè)屬性width
和height
。Swift 可以根據(jù)這兩個(gè)屬性的初始賦值0.0
自動(dòng)推導(dǎo)出它們的類型Double
。
由于這兩個(gè)存儲(chǔ)型屬性都有默認(rèn)值,結(jié)構(gòu)體Size
自動(dòng)獲得了一個(gè)逐一成員構(gòu)造器 init(width:height:)
。 你可以用它來為Size
創(chuàng)建新的實(shí)例:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實(shí)例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理,它能減少多個(gè)構(gòu)造器間的代碼重復(fù)。
構(gòu)造器代理的實(shí)現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o本身提供的其它構(gòu)造器。類則不同,它可以繼承自其它類(請(qǐng)參考繼承),這意味著類有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化。這些責(zé)任將在后續(xù)章節(jié)類的繼承和構(gòu)造過程中介紹。
對(duì)于值類型,你可以使用self.init
在自定義的構(gòu)造器中引用其它的屬于相同值類型的構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用self.init
。
注意,如果你為某個(gè)值類型定義了一個(gè)定制的構(gòu)造器,你將無法訪問到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體,則無法訪問逐一對(duì)象構(gòu)造器)。這個(gè)限制可以防止你在為值類型定義了一個(gè)更復(fù)雜的,完成了重要準(zhǔn)備構(gòu)造器之后,別人還是錯(cuò)誤的使用了那個(gè)自動(dòng)生成的構(gòu)造器。
注意:
假如你想通過默認(rèn)構(gòu)造器、逐一對(duì)象構(gòu)造器以及你自己定制的構(gòu)造器為值類型創(chuàng)建實(shí)例,我們建議你將自己定制的構(gòu)造器寫到擴(kuò)展(extension
)中,而不是跟值類型定義混在一起。想查看更多內(nèi)容,請(qǐng)查看擴(kuò)展章節(jié)。
下面例子將定義一個(gè)結(jié)構(gòu)體Rect
,用來代表幾何矩形。這個(gè)例子需要兩個(gè)輔助的結(jié)構(gòu)體Size
和Point
,它們各自為其所有的屬性提供了初始值0.0
。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
你可以通過以下三種方式為Rect
創(chuàng)建實(shí)例--使用默認(rèn)的0值來初始化origin
和size
屬性;使用特定的origin
和size
實(shí)例來初始化;使用特定的center
和size
來初始化。在下面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()
,在功能上跟沒有自定義構(gòu)造器時(shí)自動(dòng)獲得的默認(rèn)構(gòu)造器是一樣的。這個(gè)構(gòu)造器是一個(gè)空函數(shù),使用一對(duì)大括號(hào){}
來描述,它沒有執(zhí)行任何定制的構(gòu)造過程。調(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 的原點(diǎn)是 (0.0, 0.0),尺寸是 (0.0, 0.0)
第二個(gè)Rect
構(gòu)造器init(origin:size:)
,在功能上跟結(jié)構(gòu)體在沒有自定義構(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 的原點(diǎn)是 (2.0, 2.0),尺寸是 (5.0, 5.0)
第三個(gè)Rect
構(gòu)造器init(center:size:)
稍微復(fù)雜一點(diǎn)。它先通過center
和size
的值計(jì)算出origin
的坐標(biāo)。然后再調(diào)用(或代理給)init(origin:size:)
構(gòu)造器來將新的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 的原點(diǎn)是 (2.5, 2.5),尺寸是 (3.0, 3.0)
構(gòu)造器init(center:size:)
可以自己將origin
和size
的新值賦值到對(duì)應(yīng)的屬性中。然而盡量利用現(xiàn)有的構(gòu)造器和它所提供的功能來實(shí)現(xiàn)init(center:size:)
的功能,是更方便、更清晰和更直觀的方法。
注意:
如果你想用另外一種不需要自己定義init()
和init(origin:size:)
的方式來實(shí)現(xiàn)這個(gè)例子,請(qǐng)參考擴(kuò)展。
類里面的所有存儲(chǔ)型屬性--包括所有繼承自父類的屬性--都必須在構(gòu)造過程中設(shè)置初始值。
Swift 提供了兩種類型的類構(gòu)造器來確保所有類實(shí)例中存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器和便利構(gòu)造器。
指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類的構(gòu)造器來實(shí)現(xiàn)父類的初始化。
每一個(gè)類都必須擁有至少一個(gè)指定構(gòu)造器。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個(gè)條件。具體內(nèi)容請(qǐng)參考后續(xù)章節(jié)自動(dòng)構(gòu)造器的繼承。
便利構(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)造過程更清晰明了。
為了簡(jiǎn)化指定構(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è)指定構(gòu)造器結(jié)束。
一個(gè)更方便記憶的方法是:
這些規(guī)則可以通過下面圖例來說明:
如圖所示,父類中包含一個(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。
注意:
這些規(guī)則不會(huì)影響使用時(shí),如何用類去創(chuàng)建實(shí)例。任何上圖中展示的構(gòu)造器都可以用來完整創(chuàng)建對(duì)應(yīng)類的實(shí)例。這些規(guī)則只在實(shí)現(xiàn)類的定義時(shí)有影響。
下面圖例中展示了一種針對(duì)四個(gè)類的更復(fù)雜的類層級(jí)結(jié)構(gòu)。它演示了指定構(gòu)造器是如何在類層級(jí)中充當(dāng)“管道”的作用,在類的構(gòu)造器鏈上簡(jiǎn)化了類之間的相互關(guān)系。
Swift 中類的構(gòu)造過程包含兩個(gè)階段。第一個(gè)階段,每個(gè)存儲(chǔ)型屬性通過引入它們的類的構(gòu)造器來設(shè)置初始值。當(dāng)每一個(gè)存儲(chǔ)型屬性值被確定后,第二階段開始,它給每個(gè)類一次機(jī)會(huì)在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性。
兩段式構(gòu)造過程的使用讓構(gòu)造過程更安全,同時(shí)在整個(gè)類層級(jí)結(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)對(duì)某些屬性不能以0
或nil
作為合法默認(rèn)值的情況。
Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過程能順利完成:
指定構(gòu)造器必須保證它所在類引入的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器。
如上所述,一個(gè)對(duì)象的內(nèi)存只有在其所有存儲(chǔ)型屬性確定之后才能完全初始化。為了滿足這一規(guī)則,指定構(gòu)造器必須保證它所在類引入的屬性在它往上代理之前先完成初始化。
指定構(gòu)造器必須先向上代理調(diào)用父類構(gòu)造器,然后再為繼承的屬性設(shè)置新值。如果沒這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。
便利構(gòu)造器必須先代理調(diào)用同一類中的其它構(gòu)造器,然后再為任意屬性賦新值。如果沒這么做,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋。
構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值,self
的值不能被引用。
類實(shí)例在第一階段結(jié)束以前并不是完全有效,僅能訪問屬性和調(diào)用方法,一旦完成第一階段,該實(shí)例才會(huì)聲明為有效實(shí)例。
以下是兩段式構(gòu)造過程中基于上述安全檢查的構(gòu)造流程展示:
self
、修改它的屬性并調(diào)用實(shí)例方法等等。self
。下圖展示了在假定的子類和父類之間構(gòu)造的階段1:
在這個(gè)例子中,構(gòu)造過程從對(duì)子類中一個(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ù)向上做構(gòu)建代理。
一旦父類中所有屬性都有了初始值,實(shí)例的內(nèi)存被認(rèn)為是完全初始化,而階段1也已完成。
以下展示了相同構(gòu)造過程的階段2:
父類中的指定構(gòu)造器現(xiàn)在有機(jī)會(huì)進(jìn)一步來定制實(shí)例(盡管它沒有這種必要)。
一旦父類中的指定構(gòu)造器完成調(diào)用,子類的構(gòu)指定構(gòu)造器可以執(zhí)行更多的定制操作(同樣,它也沒有這種必要)。
最終,一旦子類的指定構(gòu)造器完成調(diào)用,最開始被調(diào)用的便利構(gòu)造器可以執(zhí)行更多的定制操作。
跟 Objective-C 中的子類不同,Swift 中的子類不會(huì)默認(rèn)繼承父類的構(gòu)造器。Swift 的這種機(jī)制可以防止一個(gè)父類的簡(jiǎn)單構(gòu)造器被一個(gè)更專業(yè)的子類繼承,并被錯(cuò)誤的用來創(chuàng)建子類的實(shí)例。
假如你希望自定義的子類中能實(shí)現(xiàn)一個(gè)或多個(gè)跟父類相同的構(gòu)造器--也許是為了完成一些定制的構(gòu)造過程--你可以在你定制的子類中提供和重載與父類相同的構(gòu)造器。
如果你重載的構(gòu)造器是一個(gè)指定構(gòu)造器,你可以在子類里重載它的實(shí)現(xiàn),并在自定義版本的構(gòu)造器中調(diào)用父類版本的構(gòu)造器。
如果你重載的構(gòu)造器是一個(gè)便利構(gòu)造器,你的重載過程必須通過調(diào)用同一類中提供的其它指定構(gòu)造器來實(shí)現(xiàn)。這一規(guī)則的詳細(xì)內(nèi)容請(qǐng)參考構(gòu)造器鏈。
注意:
與方法、屬性和下標(biāo)不同,在重載構(gòu)造器時(shí)你沒有必要使用關(guān)鍵字override
。
如上所述,子類不會(huì)默認(rèn)繼承父類的構(gòu)造器。但是如果特定條件可以滿足,父類構(gòu)造器是可以被自動(dòng)繼承的。在實(shí)踐中,這意味著對(duì)于許多常見場(chǎng)景你不必重載父類的構(gòu)造器,并且在盡可能安全的情況下以最小的代價(jià)來繼承父類的構(gòu)造器。
假設(shè)要為子類中引入的任意新屬性提供默認(rèn)值,請(qǐng)遵守以下2個(gè)規(guī)則:
如果子類沒有定義任何指定構(gòu)造器,它將自動(dòng)繼承所有父類的指定構(gòu)造器。
如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn)--不管是通過規(guī)則1繼承過來的,還是通過自定義實(shí)現(xiàn)的--它將自動(dòng)繼承所有父類的便利構(gòu)造器。
即使你在子類中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用。
注意:
子類可以通過部分滿足規(guī)則2的方式,使用子類便利構(gòu)造器來實(shí)現(xiàn)父類的指定構(gòu)造器。
類的指定構(gòu)造器的寫法跟值類型簡(jiǎn)單構(gòu)造器一樣:
init(parameters) {
statements
}
便利構(gòu)造器也采用相同樣式的寫法,但需要在init
關(guān)鍵字之前放置convenience
關(guān)鍵字,并使用空格將它們倆分開:
convenience init(parameters) {
statements
}
接下來的例子將在實(shí)戰(zhàn)中展示指定構(gòu)造器、便利構(gòu)造器和自動(dòng)構(gòu)造器的繼承。它定義了包含三個(gè)類Food
、RecipeIngredient
以及ShoppingListItem
的類層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。
類層次中的基類是Food
,它是一個(gè)簡(jiǎn)單的用來封裝食物名字的類。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)造器鏈:
類沒有提供一個(gè)默認(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í)例的中存儲(chǔ)型屬性都被初始化。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)的占位名字,通過代理調(diào)用同一類中定義的指定構(gòu)造器init(name: String)
并給參數(shù)name
傳值[Unnamed]
來實(shí)現(xiàn):
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
類層級(jí)中的第二個(gè)類是Food
的子類RecipeIngredient
。RecipeIngredient
類構(gòu)建了食譜中的一味調(diào)味劑。它引入了Int
類型的數(shù)量屬性quantity
(以及從Food
繼承過來的name
屬性),并且定義了兩個(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)造器鏈:
RecipeIngredient
類擁有一個(gè)指定構(gòu)造器init(name: String, quantity: Int)
,它可以用來產(chǎn)生新RecipeIngredient
實(shí)例的所有屬性值。這個(gè)構(gòu)造器一開始先將傳入的quantity
參數(shù)賦值給quantity
屬性,這個(gè)屬性也是唯一在RecipeIngredient
中新引入的屬性。隨后,構(gòu)造器將任務(wù)向上代理給父類Food
的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í)例更加方便和快捷,并且避免了使用重復(fù)的代碼來創(chuàng)建多個(gè)quantity
為 1 的RecipeIngredient
實(shí)例。這個(gè)便利構(gòu)造器只是簡(jiǎn)單的將任務(wù)代理給了同一類里提供的指定構(gòu)造器。
注意,RecipeIngredient
的便利構(gòu)造器init(name: String)
使用了跟Food
中指定構(gòu)造器init(name: String)
相同的參數(shù)。因?yàn)檫@個(gè)便利構(gòu)造器重寫要父類的指定構(gòu)造器init(name: String)
,必須在前面使用使用override
標(biāo)識(shí)。
在這個(gè)例子中,RecipeIngredient
的父類是Food
,它有一個(gè)便利構(gòu)造器init()
。這個(gè)構(gòu)造器因此也被RecipeIngredient
繼承。這個(gè)繼承的init()
函數(shù)版本跟Food
提供的版本是一樣的,除了它是將任務(wù)代理給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)
類層級(jí)中第三個(gè)也是最后一個(gè)類是RecipeIngredient
的子類,叫做ShoppingListItem
。這個(gè)類構(gòu)建了購物單中出現(xiàn)的某一種調(diào)味料。
購物單中的每一項(xiàng)總是從unpurchased
未購買狀態(tài)開始的。為了展現(xiàn)這一事實(shí),ShoppingListItem
引入了一個(gè)布爾類型的屬性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.lowercaseString)"
output += purchased ? " ?" : " ?"
return output
}
}
注意:
ShoppingListItem
沒有定義構(gòu)造器來為purchased
提供初始化值,這是因?yàn)槿魏翁砑拥劫徫飭蔚捻?xiàng)的初始狀態(tài)總是未購買。
由于它為自己引入的所有屬性都提供了默認(rèn)值,并且自己沒有定義任何構(gòu)造器,ShoppingListItem
將自動(dòng)繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器。
下圖種展示了所有三個(gè)類的構(gòu)造器鏈:
你可以使用全部三個(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 {
println(item.description)
}
// 1 x orange juice ?
// 1 x bacon ?
// 6 x eggs ?
如上所述,例子中通過字面量方式創(chuàng)建了一個(gè)新數(shù)組breakfastList
,它包含了三個(gè)新的ShoppingListItem
實(shí)例,因此數(shù)組的類型也能自動(dòng)推導(dǎo)為ShoppingListItem[]
。在數(shù)組創(chuàng)建完之后,數(shù)組中第一個(gè)ShoppingListItem
實(shí)例的名字從[Unnamed]
修改為Orange juice
,并標(biāo)記為已購買。接下來通過遍歷數(shù)組每個(gè)元素并打印它們的描述值,展示了所有項(xiàng)當(dāng)前的默認(rèn)狀態(tài)都已按照預(yù)期完成了賦值。
如果一個(gè)類,結(jié)構(gòu)體或枚舉類型的對(duì)象,在構(gòu)造自身的過程中有可能失敗,則為其定義一個(gè)可失敗構(gòu)造器,是非常有必要的。這里所指的“失敗”是指,如給構(gòu)造器傳入無效的參數(shù)值,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。
為了妥善處理這種構(gòu)造過程中可能會(huì)失敗的情況。你可以在一個(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語法為在init
關(guān)鍵字后面加添問號(hào)(init?)
。
注意:
可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其類型相同。
可失敗構(gòu)造器,在構(gòu)建對(duì)象的過程中,創(chuàng)建一個(gè)其自身類型為可選類型的對(duì)象。你通過return nil
語句,來表明可失敗構(gòu)造器在何種情況下“失敗”。
注意:
嚴(yán)格來說,構(gòu)造器都不支持返回值。因?yàn)闃?gòu)造器本身的作用,只是為了能確保對(duì)象自身能被正確構(gòu)建。所以即使你在表明可失敗構(gòu)造器,失敗的這種情況下,用到了
return nil
。也不要在表明可失敗構(gòu)造器成功的這種情況下,使用關(guān)鍵字return
。
下例中,定義了一個(gè)名為Animal
的結(jié)構(gòu)體,其中有一個(gè)名為species
的,String
類型的常量屬性。同時(shí)該結(jié)構(gòu)體還定義了一個(gè),帶一個(gè)String
類型參數(shù)species
的,可失敗構(gòu)造器。這個(gè)可失敗構(gòu)造器,被用來檢查傳入的參數(shù)是否為一個(gè)空字符串,如果為空字符串,則該可失敗構(gòu)造器,構(gòu)建對(duì)象失敗,否則成功。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
你可以通過該可失敗構(gòu)造器來構(gòu)建一個(gè)Animal的對(duì)象,并檢查其構(gòu)建過程是否成功。
let someCreature = Animal(species: "Giraffe")
// someCreature 的類型是 Animal? 而不是 Animal
if let giraffe = someCreature {
println("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
如果你給該可失敗構(gòu)造器傳入一個(gè)空字符串作為其參數(shù),則該可失敗構(gòu)造器失敗。
let anonymousCreature = Animal(species: "")
// anonymousCreature 的類型是 Animal?, 而不是 Animal
if anonymousCreature == nil {
println("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
注意:
空字符串(
""
)和一個(gè)值為nil
的可選類型的字符串是兩個(gè)完全不同的概念。上例中的空字符串(""
)其實(shí)是一個(gè)有效的,非可選類型的字符串。這里我們只所以讓Animal
的可失敗構(gòu)造器,構(gòu)建對(duì)象失敗,只是因?yàn)閷?duì)于Animal
這個(gè)類的species
屬性來說,它更適合有一個(gè)具體的值,而不是空字符串。
你可以通過構(gòu)造一個(gè)帶一個(gè)或多個(gè)參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。還能在參數(shù)不滿足你所期望的條件時(shí),導(dǎo)致構(gòu)造失敗。
下例中,定義了一個(gè)名為TemperatureUnit的枚舉類型。其中包含了三個(gè)可能的枚舉成員(Kelvin
,Celsius
,和 Fahrenheit
)和一個(gè)被用來找到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)造器傳遞合適的參數(shù)來獲取這三個(gè)枚舉成員中相匹配的其中一個(gè)枚舉成員。當(dāng)參數(shù)的值不能與任意一枚舉成員相匹配時(shí),該枚舉類型的構(gòu)建過程失?。?/p>
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
println("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 {
println("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器init?(rawValue:)
,該可失敗構(gòu)造器有一個(gè)名為rawValue
的默認(rèn)參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和枚舉類型成員所帶的原始值匹配,則該構(gòu)造器構(gòu)造一個(gè)帶此原始值的枚舉成員,否則構(gòu)造失敗。
因此上面的 TemperatureUnit的例子可以重寫為:
enum TemperatureUnit: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
println("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 {
println("This is not a defined temperature unit, so initialization failed.")
}
// prints "This is not a defined temperature unit, so initialization failed."
值類型(如結(jié)構(gòu)體或枚舉類型)的可失敗構(gòu)造器,對(duì)何時(shí)何地觸發(fā)構(gòu)造失敗這個(gè)行為沒有任何的限制。比如在前面的例子中,結(jié)構(gòu)體Animal
的可失敗構(gòu)造器觸發(fā)失敗的行為,甚至發(fā)生在species
屬性的值被初始化以前。而對(duì)類而言,就沒有那么幸運(yùn)了。類的可失敗構(gòu)造器只能在所有的類屬性被初始化后和所有類之間的構(gòu)造器之間的代理調(diào)用發(fā)生完后觸發(fā)失敗行為。
下例子中,定義了一個(gè)名為Product
的類,其內(nèi)部結(jié)構(gòu)和結(jié)構(gòu)體Animal
很相似,內(nèi)部也有一個(gè)名為name
的String
類型的屬性。由于該屬性的值同樣不能為空字符串,所以我們加入了可失敗構(gòu)造器來確保該類滿足上述條件。但由于Product
類不是一個(gè)結(jié)構(gòu)體,所以當(dāng)想要在該類中添加可失敗構(gòu)造器觸發(fā)失敗條件時(shí),必須確保name
屬性被初始化。因此我們把name
屬性的String
類型做了一點(diǎn)點(diǎn)小小的修改,把其改為隱式解析可選類型(String!
),來確保可失敗構(gòu)造器觸發(fā)失敗條件時(shí),所有類屬性都被初始化了。因?yàn)樗锌蛇x類型都有一個(gè)默認(rèn)的初始值nil
。因此最后Product
類可寫為:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
因?yàn)?code>name屬性是一個(gè)常量,所以一旦Product
類構(gòu)造成功,name
屬性肯定有一個(gè)非nil
的值。因此完全可以放心大膽的直接訪問Product
類的name
屬性,而不用考慮去檢查name
屬性是否有值。
if let bowTie = Product(name: "bow tie") {
// 不需要檢查 bowTie.name == nil
println("The product's name is \(bowTie.name)")
}
// 打印 "The product's name is bow tie"
可失敗構(gòu)造器同樣滿足在構(gòu)造器鏈中所描述的構(gòu)造規(guī)則。其允許在同一類,結(jié)構(gòu)體和枚舉中橫向代理其他的可失敗構(gòu)造器。類似的,子類的可失敗構(gòu)造器也能向上代理基類的可失敗構(gòu)造器。
無論是向上代理還是橫向代理,如果你代理的可失敗構(gòu)造器,在構(gòu)造過程中觸發(fā)了構(gòu)造失敗的行為,整個(gè)構(gòu)造過程都將被立即終止,接下來任何的構(gòu)造代碼都將不會(huì)被執(zhí)行。
注意:
可失敗構(gòu)造器也可以代理調(diào)用其它的非可失敗構(gòu)造器。通過這個(gè)方法,你可以為已有的構(gòu)造過程加入構(gòu)造失敗的條件。
下面這個(gè)例子,定義了一個(gè)名為CartItem
的Product
類的子類。這個(gè)類建立了一個(gè)在線購物車中的物品的模型,它有一個(gè)名為quantity
的常量參數(shù),用來表示該物品的數(shù)量至少為1:
class CartItem: Product {
let quantity: Int!
init?(name: String, quantity: Int) {
super.init(name: name)
if quantity < 1 { return nil }
self.quantity = quantity
}
}
和Product
類中的name
屬性相類似的,CartItem
類中的quantity
屬性的類型也是一個(gè)隱式解析可選類型,只不過由(String!
)變?yōu)榱耍?code>Int!)。這樣做都是為了確保在構(gòu)造過程中,該屬性在被賦予特定的值之前能有一個(gè)默認(rèn)的初始值nil。
可失敗構(gòu)造器總是先向上代理調(diào)用基類,Product
的構(gòu)造器 init(name:)
。這滿足了可失敗構(gòu)造器在觸發(fā)構(gòu)造失敗這個(gè)行為前必須總是執(zhí)行構(gòu)造代理調(diào)用這個(gè)條件。
如果由于name
的值為空而導(dǎo)致基類的構(gòu)造器在構(gòu)造過程中失敗。則整個(gè)CartIem
類的構(gòu)造過程都將失敗,后面的子類的構(gòu)造過程都將不會(huì)被執(zhí)行。如果基類構(gòu)建成功,則繼續(xù)運(yùn)行子類的構(gòu)造器代碼。
如果你構(gòu)造了一個(gè)CartItem
對(duì)象,并且該對(duì)象的name
屬性不為空以及quantity
屬性為1或者更多,則構(gòu)造成功:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
println("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2"
如果你構(gòu)造一個(gè)CartItem
對(duì)象,其quantity
的值0
, 則CartItem
的可失敗構(gòu)造器觸發(fā)構(gòu)造失敗的行為:
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
println("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
println("Unable to initialize zero shirts")
}
// 打印 "Unable to initialize zero shirts"
類似的, 如果你構(gòu)造一個(gè)CartItem
對(duì)象,但其name
的值為空, 則基類Product
的可失敗構(gòu)造器將觸發(fā)構(gòu)造失敗的行為,整個(gè)CartItem
的構(gòu)造行為同樣為失?。?/p>
if let oneUnnamed = CartItem(name: "", quantity: 1) {
println("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
println("Unable to initialize one unnamed product")
}
// 打印 "Unable to initialize one unnamed product"
就如同其它構(gòu)造器一樣,你也可以用子類的可失敗構(gòu)造器覆蓋基類的可失敗構(gòu)造器?;蛘吣阋部梢杂米宇惖姆强墒?gòu)造器覆蓋一個(gè)基類的可失敗構(gòu)造器。這樣做的好處是,即使基類的構(gòu)造器為可失敗構(gòu)造器,但當(dāng)子類的構(gòu)造器在構(gòu)造過程不可能失敗時(shí),我們也可以把它修改過來。
注意當(dāng)你用一個(gè)子類的非可失敗構(gòu)造器覆蓋了一個(gè)父類的可失敗構(gòu)造器時(shí),子類的構(gòu)造器將不再能向上代理父類的可失敗構(gòu)造器。一個(gè)非可失敗的構(gòu)造器永遠(yuǎn)也不能代理調(diào)用一個(gè)可失敗構(gòu)造器。
注意:
你可以用一個(gè)非可失敗構(gòu)造器覆蓋一個(gè)可失敗構(gòu)造器,但反過來卻行不通。
下例定義了一個(gè)名為Document
的類,這個(gè)類中的name
屬性允許為nil
和一個(gè)非空字符串,但不能是一個(gè)空字符串:
class Document {
var name: String?
// 該構(gòu)造器構(gòu)建了一個(gè)name屬性值為nil的document對(duì)象
init() {}
// 該構(gòu)造器構(gòu)建了一個(gè)name屬性值為非空字符串的document對(duì)象
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
下面這個(gè)例子,定義了一個(gè)名為AutomaticallyNamedDocument
的Document
類的子類。這個(gè)子類覆蓋了基類的兩個(gè)指定構(gòu)造器。確保了不論在何種情況下name
屬性總是有一個(gè)非空字符串[Untitled]
的值。
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
AutomaticallyNamedDocument
用一個(gè)非可失敗構(gòu)造器init(name:)
,覆蓋了基類的可失敗構(gòu)造器init?(name:)
。因?yàn)樽宇愑貌煌姆椒ㄌ幚砹?code>name屬性的值為一個(gè)空字符串的這種情況。所以子類將不再需要一個(gè)可失敗的構(gòu)造器。
通常來說我們通過在init
關(guān)鍵字后添加問號(hào)的方式來定義一個(gè)可失敗構(gòu)造器,但你也可以使用通過在init
后面添加驚嘆號(hào)的方式來定義一個(gè)可失敗構(gòu)造器(init!)
,該可失敗構(gòu)造器將會(huì)構(gòu)建一個(gè)特定類型的隱式解析可選類型的對(duì)象。
你可以在 init?
構(gòu)造器中代理調(diào)用 init!
構(gòu)造器,反之亦然。你也可以用 init?
覆蓋 init!
,反之亦然。你還可以用 init
代理調(diào)用init!
,但這會(huì)觸發(fā)一個(gè)斷言:是否 init!
構(gòu)造器會(huì)觸發(fā)構(gòu)造失???
在類的構(gòu)造器前添加required
修飾符表明所有該類的子類都必須實(shí)現(xiàn)該構(gòu)造器:
class SomeClass {
required init() {
// 在這里添加該必要構(gòu)造器的實(shí)現(xiàn)代碼
}
}
當(dāng)子類覆蓋基類的必要構(gòu)造器時(shí),必須在子類的構(gòu)造器前同樣添加required
修飾符以確保當(dāng)其它類繼承該子類時(shí),該構(gòu)造器同為必要構(gòu)造器。在覆蓋基類的必要構(gòu)造器時(shí),不需要添加override
修飾符:
class SomeSubclass: SomeClass {
required init() {
// 在這里添加子類必要構(gòu)造器的實(shí)現(xiàn)代碼
}
}
注意:
如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的需求,則你無需顯示的在子類中提供必要構(gòu)造器的實(shí)現(xiàn)。
如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要特別的定制或準(zhǔn)備,你就可以使用閉包或全局函數(shù)來為其屬性提供定制的默認(rèn)值。每當(dāng)某個(gè)屬性所屬的新類型實(shí)例創(chuàng)建時(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)值進(jì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)。這是用來告訴 Swift 需要立刻執(zhí)行此閉包。如果你忽略了這對(duì)括號(hào),相當(dāng)于是將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。
注意:
如果你使用閉包來初始化屬性的值,請(qǐng)記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒有初始化。這意味著你不能夠在閉包里訪問其它的屬性,就算這個(gè)屬性有默認(rèn)值也不允許。同樣,你也不能使用隱式的self
屬性,或者調(diào)用其它的實(shí)例方法。
下面例子中定義了一個(gè)結(jié)構(gòu)體Checkerboard
,它構(gòu)建了西洋跳棋游戲的棋盤:
西洋跳棋游戲在一副黑白格交替的 10x10 的棋盤中進(jìn)行。為了呈現(xiàn)這副游戲棋盤,Checkerboard
結(jié)構(gòu)體定義了一個(gè)屬性boardColors
,它是一個(gè)包含 100 個(gè)布爾值的數(shù)組。數(shù)組中的某元素布爾值為true
表示對(duì)應(yīng)的是一個(gè)黑格,布爾值為false
表示對(duì)應(yīng)的是一個(gè)白格。數(shù)組中第一個(gè)元素代表棋盤上左上角的格子,最后一個(gè)元素代表棋盤上右下角的格子。
boardColor
數(shù)組是通過一個(gè)閉包來初始化和組裝顏色值的:
struct Checkerboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...10 {
for j in 1...10 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
return boardColors[(row * 10) + column]
}
}
每當(dāng)一個(gè)新的Checkerboard
實(shí)例創(chuàng)建時(shí),對(duì)應(yīng)的賦值閉包會(huì)執(zhí)行,一系列顏色值會(huì)被計(jì)算出來作為默認(rèn)值賦值給boardColors
。上面例子中描述的閉包將計(jì)算出棋盤中每個(gè)格子合適的顏色,將這些顏色值保存到一個(gè)臨時(shí)數(shù)組temporaryBoard
中,并在構(gòu)建完成時(shí)將此數(shù)組作為閉包返回值返回。這個(gè)返回的值將保存到boardColors
中,并可以通squareIsBlackAtRow
這個(gè)工具函數(shù)來查詢。
let board = Checkerboard()
println(board.squareIsBlackAtRow(0, column: 1))
// 輸出 "true"
println(board.squareIsBlackAtRow(9, column: 9))
// 輸出 "false"
更多建議: