Initialization in Swift

簡(jiǎn)介

*自定義構(gòu)造過程

*默認(rèn)構(gòu)造器

*值類型的構(gòu)造器代理

*類的繼承和構(gòu)造過程

*可失敗構(gòu)造器

*必需構(gòu)造器

*通過閉包和函數(shù)來(lái)設(shè)置屬性的默認(rèn)值

(2014-8-8更新至beta5語(yǔ)法)

(2014-10-24更新至Swift1.1)

##簡(jiǎn)介

構(gòu)造過程是為了使用某個(gè)類速梗、結(jié)構(gòu)體或枚舉類型的實(shí)例而進(jìn)行的準(zhǔn)備過程。這個(gè)過程包含了為實(shí)例中的每個(gè)屬性設(shè)置初始值和為其執(zhí)行必要的準(zhǔn)備和初始化任務(wù)些举。

Swift中的構(gòu)造器不像Objective-C那樣有返回值窗价,但是跟C++有點(diǎn)像:所有的構(gòu)造器都以init命名腋舌,用參數(shù)列表來(lái)區(qū)分各個(gè)構(gòu)造器

structFahrenheit{

var temperature =16.0

init() {

temperature =32.0

}

}

var f =Fahrenheit()

Fahrenheit是一個(gè)結(jié)構(gòu)體,與類一樣,Swift中的值類型也有構(gòu)造器和普通方法钱烟。上面的代碼先是定義了temperature屬性的默認(rèn)值,然后又在構(gòu)造器中將其賦值,最后temperature屬性的值為32拴袭。對(duì)于temperature這種存儲(chǔ)型屬性读第,無(wú)論定義默認(rèn)值還是在構(gòu)造器中賦值,最終它們實(shí)現(xiàn)的效果是一樣的拥刻。

給存儲(chǔ)型類型屬性賦默認(rèn)值或在初始構(gòu)造器中設(shè)置初始值時(shí)怜瞒,此屬性的屬性觀察者不會(huì)被調(diào)用

##自定義構(gòu)造過程

你可以定義一些帶參數(shù)的構(gòu)造器

structCelsius{

var temperatureInCelsius:Double=0.0

init(fromFahrenheit fahrenheit:Double) {

temperatureInCelsius = (fahrenheit -32.0) /1.8

}

init(fromKelvin kelvin:Double) {

temperatureInCelsius = kelvin -273.15

}

}


構(gòu)造器的參數(shù)也跟Swift中的方法定義(注意不是函數(shù))一樣,也分內(nèi)部和外部參數(shù)名般哼。上面代碼中兩個(gè)構(gòu)造器外部參數(shù)名不同吴汪,調(diào)用構(gòu)造器的時(shí)候也是通過外部參數(shù)名來(lái)區(qū)分的:

let boilingPointOfWater = Celsius(fromFahrenheit:212.0)

// boilingPointOfWater.temperatureInCelsius 是 100.0

let freezingPointOfWater = Celsius(fromKelvin:273.15)

// freezingPointOfWater.temperatureInCelsius 是 0.0”

如果不寫外部參數(shù)名那么外部參數(shù)名就等于內(nèi)部參數(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

}

}

let magenta = Color(red:1.0,green:0.0,blue:1.0)

如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供外部名字,你還可以使用下劃線_來(lái)顯示描述它的外部名:

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

你會(huì)發(fā)現(xiàn)構(gòu)造器的第一個(gè)內(nèi)部參數(shù)名也會(huì)默認(rèn)作為其外部參數(shù)名供調(diào)用蒸眠,這一點(diǎn)與方法不同(方法不會(huì)默認(rèn)將第一個(gè)內(nèi)部參數(shù)名作為外部參數(shù)名使用)漾橙,因?yàn)榉椒梢栽诮Y(jié)尾加上介詞來(lái)烘托出第一個(gè)參數(shù)的名字,這樣就不需要為第一個(gè)參數(shù)弄一個(gè)外部參數(shù)名了楞卡,但是構(gòu)造器只能用init關(guān)鍵字來(lái)定義霜运。

如果你定制的類型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性–不管是因?yàn)樗鼰o(wú)法在初始化時(shí)賦值,還是因?yàn)樗梢栽谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空–你都需要將它定義為可選類型optional type蒋腮√约瘢可選類型的屬性將自動(dòng)初始化為空nil,表示這個(gè)屬性是故意在初始化時(shí)設(shè)置為空的徽惋。

class SurveyQuestion {

let 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)修改常量屬性的值代咸。盡管text屬性是常量蹈丸,我們?nèi)匀豢梢栽谄漕惖臉?gòu)造器中設(shè)置它的值。對(duì)某個(gè)類實(shí)例來(lái)說呐芥,它的常量屬性只能在定義它的類的構(gòu)造過程中修改逻杖;不能在子類中修改。

##默認(rèn)構(gòu)造器

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í)例:

class ShoppingListItem{

var name:String?

var quantity =1

var purchased =false

}

var item = ShoppingListItem()

ShoppingListItem類沒有父類(是基類),所有屬性都有默認(rèn)值(可選屬性默認(rèn)值為nil)滨攻,所以可以直接調(diào)用默認(rèn)的無(wú)參數(shù)構(gòu)造器來(lái)初始化够话。

除上面提到的默認(rèn)構(gòu)造器蓝翰,如果結(jié)構(gòu)體對(duì)所有存儲(chǔ)型屬性提供了默認(rèn)值且自身沒有提供定制的構(gòu)造器,它們能自動(dòng)獲得一個(gè)逐一成員構(gòu)造器(Memberwise Initializers)

struct Size {

varwidth=0.0,height=0.0

}

let twoByTwo = Size(width:2.0,height:2.0)

逐一成員構(gòu)造器是用來(lái)初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法女嘲。我們?cè)谡{(diào)用逐一成員構(gòu)造器時(shí)畜份,通過與成員屬性名相同的參數(shù)名進(jìn)行傳值來(lái)完成對(duì)成員屬性的初始賦值。

##值類型的構(gòu)造器代理

構(gòu)造器可以通過調(diào)用其它構(gòu)造器來(lái)完成實(shí)例的部分構(gòu)造過程欣尼。這一過程稱為構(gòu)造器代理爆雹,它能減少多個(gè)構(gòu)造器間的代碼重復(fù):

struct Size {

varwidth=0.0, height =0.0

}

struct Point {

varx =0.0, y =0.0

}

struct Rect {

varorigin = Point()

varsize = 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òu)造器(如init(center: Point, size: Size))媒至,Swift不會(huì)再為結(jié)構(gòu)體生成一個(gè)默認(rèn)構(gòu)造器和逐一成員構(gòu)造器顶别,所以我們自己定義了init()和init(origin: Point, size: Size)谷徙,他們與自動(dòng)生成的默認(rèn)構(gòu)造器和逐一成員構(gòu)造器是一樣的拒啰。這樣子會(huì)顯得很麻煩,我們可以將自定義構(gòu)造器init(center: Point, size: Size)寫在結(jié)構(gòu)體Rect的擴(kuò)展(extension)里完慧,這樣就不用自己把默認(rèn)構(gòu)造器和逐一成員構(gòu)造器寫一遍了谋旦。

構(gòu)造器init(center:size:)先通過center和size的值計(jì)算出origin的坐標(biāo)。然后再調(diào)用(或代理給)init(origin:size:)構(gòu)造器來(lái)將新的origin和size值賦值到對(duì)應(yīng)的屬性中屈尼。因?yàn)橹殿愋停ńY(jié)構(gòu)體和枚舉類型)不支持繼承册着,所以構(gòu)造器代理的過程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o本身提供的其它構(gòu)造器:你只能在構(gòu)造器內(nèi)部調(diào)用self.init

##類的繼承和構(gòu)造過程

類里面的所有存儲(chǔ)型屬性–包括所有繼承自父類的屬性–都必須在構(gòu)造過程中設(shè)置初始值脾歧。

Swift 提供了兩種類型的類構(gòu)造器來(lái)確保所有類實(shí)例中存儲(chǔ)型屬性都能獲得初始值甲捏,它們分別是指定構(gòu)造器(Designated Initializers)和便利構(gòu)造器(Convenience Initializers)。

###指定構(gòu)造器和便利構(gòu)造器

指定構(gòu)造器是類中最主要的構(gòu)造器鞭执。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性司顿,并根據(jù)父類鏈往上調(diào)用父類的構(gòu)造器來(lái)實(shí)現(xiàn)父類的初始化。每一個(gè)類都必須擁有至少一個(gè)指定構(gòu)造器兄纺。

便利構(gòu)造器是類中比較次要的大溜、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來(lái)調(diào)用同一個(gè)類中的指定構(gòu)造器估脆,并為其參數(shù)提供默認(rèn)值钦奋。你也可以定義便利構(gòu)造器來(lái)創(chuàng)建一個(gè)特殊用途或特定輸入的實(shí)例。

類的指定構(gòu)造器的寫法跟值類型簡(jiǎn)單構(gòu)造器一樣疙赠,便利構(gòu)造器也采用相同樣式的寫法付材,但需要在init關(guān)鍵字之前放置convenience關(guān)鍵字:

convenience init(parameters) {

statements

}

###構(gòu)造器鏈

為了簡(jiǎn)化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,Swift 采用以下三條規(guī)則來(lái)限制構(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è)更方便記憶的方法是:

指定構(gòu)造器必須總是向上代理

便利構(gòu)造器必須總是橫向代理


舉個(gè)栗子:

class Food{

var name:String

init(name:String) {

self.name = name

}

convenienceinit() {

self.init(name:"[Unnamed]")

}

}

letnamedMeat =Food(name:"Bacon")

// namedMeat 的名字是 "Bacon”

letmysteryMeat =Food()

// mysteryMeat 的名字是 [Unnamed]


###兩段式構(gòu)造過程

Swift 中類的構(gòu)造過程包含兩個(gè)階段限佩。第一個(gè)階段葵诈,每個(gè)存儲(chǔ)型屬性通過引入它們的類的構(gòu)造器來(lái)設(shè)置初始值裸弦。當(dāng)每一個(gè)存儲(chǔ)型屬性值被確定后,第二階段開始作喘,它給每個(gè)類一次機(jī)會(huì)在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性理疙。

下圖展示了在假定的子類和父類之間構(gòu)造的階段1:


指定構(gòu)造器將確保所有子類的屬性都有值,然后它將調(diào)用父類的指定構(gòu)造器泞坦,并沿著造器鏈一直往上完成父類的構(gòu)建過程窖贤。一旦父類中所有屬性都有了初始值,實(shí)例的內(nèi)存被認(rèn)為是完全初始化贰锁,而階段1也已完成赃梧。

以下展示了相同構(gòu)造過程的階段2:


父類中的指定構(gòu)造器現(xiàn)在有機(jī)會(huì)進(jìn)一步來(lái)定制實(shí)例(盡管它沒有這種必要)。

一旦父類中的指定構(gòu)造器完成調(diào)用豌熄,子類的構(gòu)指定構(gòu)造器可以執(zhí)行更多的定制操作(同樣授嘀,它也沒有這種必要)。

最終锣险,一旦子類的指定構(gòu)造器完成調(diào)用蹄皱,最開始被調(diào)用的便利構(gòu)造器可以執(zhí)行更多的定制操作。

兩段式構(gòu)造過程是基于安全檢查的芯肤,可以簡(jiǎn)單的理解為:

指定構(gòu)造器初始化順序:初始化類自己引入的屬性->向上代理調(diào)用父類指定構(gòu)造器->為繼承的屬性設(shè)置新值

便利構(gòu)造器初始化順序:代理調(diào)用同一類中的其它構(gòu)造器->為任意屬性賦新值

構(gòu)造器在第一階段構(gòu)造完成之前巷折,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值崖咨,也不能引用self的值锻拘。

###構(gòu)造器的繼承和重載

跟 Objective-C 中的子類不同,Swift 中的子類不會(huì)默認(rèn)繼承父類的構(gòu)造器击蹲。這是為了防止你想初始化一個(gè)很牛逼的類署拟,但是調(diào)用的卻是它繼承于父類的菜逼構(gòu)造器,那將會(huì)是個(gè)悲劇际邻。

但是如果特定條件可以滿足芯丧,父類構(gòu)造器是可以被自動(dòng)繼承的:

如果子類沒有定義任何指定構(gòu)造器,它將自動(dòng)繼承所有父類的指定構(gòu)造器世曾。

如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn)–不管是通過規(guī)則1繼承過來(lái)的缨恒,還是通過自定義實(shí)現(xiàn)的–它將自動(dòng)繼承所有父類的便利構(gòu)造器。

即使你在子類中添加了更多的便利構(gòu)造器轮听,這兩條規(guī)則仍然適用骗露。子類可以通過定義便利構(gòu)造器來(lái)實(shí)現(xiàn)父類中的指定構(gòu)造器,來(lái)部分滿足規(guī)則2

如果你需要在子類中重寫一個(gè)父類的指定構(gòu)造器(包括自動(dòng)生成的默認(rèn)構(gòu)造器)血巍,無(wú)論子類中的構(gòu)造器是指定構(gòu)造器還是便利構(gòu)造器萧锉,都需要在子類定義重載的構(gòu)造器前加上override修飾;如果你需要在子類中重寫一個(gè)父類的便利構(gòu)造器述寡,根據(jù)構(gòu)造器鏈柿隙,父類的便利構(gòu)造器不會(huì)被子類直接調(diào)用叶洞,所以不必在子類重寫構(gòu)造器的定義前用override修飾。這是Xcode6beta5新修訂的規(guī)則禀崖,在以前的版本中重載構(gòu)造器不用override修飾衩辟。

還記得之前指定的Food類吧,現(xiàn)在它多了一個(gè)子類RecipeIngredient:

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)

}

}

可以看出來(lái)RecipeIngredient類的指定構(gòu)造器和便利構(gòu)造器都符合兩段式構(gòu)造安全檢查波附,并且便利構(gòu)造器跟Food類中的指定構(gòu)造器具有相同的參數(shù)艺晴,盡管RecipeIngredient這個(gè)構(gòu)造器是便利構(gòu)造器,RecipeIngredient依然提供了對(duì)所有父類指定構(gòu)造器的實(shí)現(xiàn)掸屡。因此封寞,RecipeIngredient也能自動(dòng)繼承了所有父類的便利構(gòu)造器(也就是init()):


上圖中RecipeIngredient類繼承的init()函數(shù)版本跟Food提供的版本是一樣的,除了它是將任務(wù)代理給RecipeIngredient版本的init(name: String)而不是Food提供的版本仅财。

食材都已經(jīng)建立好了狈究,下面開始采購(gòu)吧!我們需要一個(gè)購(gòu)物單满着,購(gòu)物單中每一項(xiàng)是這樣子的:

class ShoppingListItem:RecipeIngredient {

var purchased =false

var description: String {

var output ="\(quantity) x \(name.lowercaseString)"

output += purchased ?" ?":" ?"

returnoutput

}

}

由于它為自己引入的所有屬性都提供了默認(rèn)值谦炒,并且自己沒有定義任何構(gòu)造器贯莺,ShoppingListItem將自動(dòng)繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器风喇。


你可以使用全部三個(gè)繼承來(lái)的構(gòu)造器來(lái)創(chuàng)建ShoppingListItem的新實(shí)例:

var breakfastList = [

ShoppingListItem(),

ShoppingListItem(name:"Bacon"),

ShoppingListItem(name:"Eggs", quantity:6),

]

breakfastList[0].name="Orange juice"

breakfastList[0].purchased= true

for item in breakfastList {

println(item.description)

}

// 1 x orange juice ?

// 1 x bacon ?

// 6 x eggs ?

##可失敗構(gòu)造器(Failable Initializers)

有時(shí)候定義一個(gè)構(gòu)造器可以失敗的類,結(jié)構(gòu)體或枚舉是很有用的缕探。這樣的失敗可能被非法的初始化參數(shù)魂莫,所需外部資源的缺失或一些其他阻止初始化成功的情況觸發(fā)。

為了應(yīng)付初始化可能失敗的情形爹耗,需要在定義類耙考,結(jié)構(gòu)體或枚舉時(shí)再定義一個(gè)或多個(gè)可失敗構(gòu)造器。在init關(guān)鍵字后面放一個(gè)問號(hào)(init?)即可寫出一個(gè)可失敗構(gòu)造器潭兽。

你不能定義具有相同參數(shù)類型和參數(shù)名稱的可失敗構(gòu)造器和非可失敗構(gòu)造器

可失敗構(gòu)造器返回它初始化類型的可選值倦始。你要在可失敗構(gòu)造器中可能觸發(fā)失敗的地方返回nil。

嚴(yán)格的來(lái)說山卦,構(gòu)造器不返回值鞋邑。更確切地說,它們的角色是確保self在構(gòu)造過程結(jié)束時(shí)被完整正確的初始化账蓉。雖然你寫下return nil來(lái)觸發(fā)一個(gè)構(gòu)造過程的失敗枚碗,但你不要使用return關(guān)鍵字來(lái)指明構(gòu)造過程的成功。

下面的例子定義了一個(gè)叫做Animal的結(jié)構(gòu)體铸本,它有一個(gè)叫做species的String常量屬性肮雨。Animal結(jié)構(gòu)體還定義了一個(gè)可失敗構(gòu)造器,它只接收一個(gè)species參數(shù)箱玷。這個(gè)構(gòu)造器檢查被傳遞過來(lái)的species參數(shù)是否為空字符串:如果是空字符串那就觸發(fā)構(gòu)造過程失敗怨规,否則species屬性會(huì)被賦值陌宿,構(gòu)造過程成功:

struct Animal{

let species:String

init?(species:String) {

if species.isEmpty {return nil }

self.species = species

}

}

你可以試著使用可失敗構(gòu)造器來(lái)初始化一個(gè)Animal對(duì)象來(lái)檢驗(yàn)構(gòu)造過程是否成功:

let someCreature =Animal(species:"Giraffe")

// someCreature is of type Animal?, not Animal

if let giraffe = someCreature {

println("An animal was initialized with a species of\(giraffe.species)")

}

// prints "An animal was initialized with a species of Giraffe"

如果你給可失敗構(gòu)造器傳遞一個(gè)空字符串作為參數(shù),那么將會(huì)觸發(fā)構(gòu)造過程失敳ǚ帷:

let anonymousCreature =Animal(species:"")

// anonymousCreatureisoftypeAnimal?,notAnimal

if anonymousCreature ==nil{

println("The anonymous creature could not be initialized")

}

// prints"The anonymous creature could not be initialized"

這里注意下空字符串與nil的區(qū)別限番。空字符串是合法的呀舔,它并不像nil那樣代表了某種類型值得缺失弥虐。但在Animal中我們必須設(shè)定一個(gè)有意義的species屬性,它不能是空字符串媚赖。

###枚舉的可失敗構(gòu)造器

你可以使用可失敗構(gòu)造器根據(jù)一或多個(gè)參數(shù)來(lái)選擇合適的枚舉成員霜瘪。如果傳入的參數(shù)沒能匹配到合適的枚舉成員,構(gòu)造就會(huì)失敗惧磺。

下面的例子定義了一個(gè)叫做TemperatureUnit的枚舉颖对,有三個(gè)可能的情況 (Kelvin,Celsius, 和Fahrenheit)∧グ可失敗構(gòu)造器用于在表現(xiàn)一個(gè)溫度符號(hào)字符時(shí)找到一個(gè)合適的枚舉成員:

enum TemperatureUnit{

case Kelvin, Celsius, Fahrenheit

init?(symbol:Character) {

switch symbol {

case"K":

self= .Kelvin

case"C":

self= .Celsius

case"F":

self= .Fahrenheit

default:

returnnil

}

}

}

你可以使用這個(gè)可失敗構(gòu)造器來(lái)為三種可能的狀況選擇合適的枚舉成員缤底,也可在參數(shù)不能匹配任何狀況時(shí)讓構(gòu)造過程失敗:

let fahrenheitUnit =TemperatureUnit(symbol:"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(symbol:"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."

###帶有原始值枚舉的可失敗構(gòu)造器

帶有原始值的枚舉會(huì)自動(dòng)得到一個(gè)可失敗構(gòu)造器:init?(rawValue:)番捂,它接受rawValue參數(shù)來(lái)匹配一個(gè)枚舉成員个唧,如果無(wú)法匹配就出發(fā)構(gòu)造過程失敗。

發(fā)揮init?(rawValue:)的優(yōu)勢(shì)將上面的例子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."

###類的可失敗構(gòu)造器

值類型的可失敗構(gòu)造器可以在任何地方觸發(fā)構(gòu)造過程失敗设预。在上面的Animal結(jié)構(gòu)體例子中徙歼,構(gòu)造器在species屬性被賦值前就觸發(fā)了構(gòu)造過程失敗,位置很靠前鳖枕。

對(duì)于類魄梯,可失敗構(gòu)造器只能在所有存儲(chǔ)型屬性被賦初值并且任意構(gòu)造器代理發(fā)生后才觸發(fā)構(gòu)造過程失敗。

下面給出的例子展現(xiàn)了如何使用隱式解析可選屬性來(lái)滿足這個(gè)可失敗類構(gòu)造器的需求:

class Product{

let name: String!

init?(name: String) {

if name.isEmpty {return nil }

self.name=name

?}

}

上面定義的Product類非常類似之前見過的Animal結(jié)構(gòu)體宾符。Product類有一個(gè)不能為空值的字符串常量屬性name酿秸。為了強(qiáng)制滿足這個(gè)要求,Product類使用可失敗構(gòu)造器來(lái)確保name屬性值在構(gòu)造過程成功前不為空魏烫。

然而Product是一個(gè)類而不是結(jié)構(gòu)體辣苏。這意味著它不像Animal,任何Product類的可失敗構(gòu)造器都必須在觸發(fā)構(gòu)造過程失敗之前為name屬性提供一個(gè)初值则奥。

在上面的例子中考润,Product類的name屬性被定義成隱式解析可選的字符串類型(String!)。因?yàn)樗且粋€(gè)可選類型读处,這意味著構(gòu)造過程中name屬性在被賦值前有一個(gè)默認(rèn)值nil糊治。這個(gè)默認(rèn)值nil意味著Product類中所有屬性都有一個(gè)合法的初值。結(jié)果就是Product類的可失敗構(gòu)造器如果被傳遞的是一個(gè)空字符串罚舱,它能在構(gòu)造器開始時(shí)就觸發(fā)一個(gè)構(gòu)造過程失敗井辜。

因?yàn)閚ame屬性是常量绎谦,你可以確信在構(gòu)造過程成功后它將永遠(yuǎn)不是nil。盡管它被定義為隱式解析可選類型粥脚,你可以永遠(yuǎn)自信地獲取它的隱式解析值窃肠,而不用檢驗(yàn)是否為nil:

if let bowTie = Product(name:"bow tie") {

// no need tocheckif bowTie.name == nil

println("The product's name is \(bowTie.name)")

}

// prints"The product's name is bow tie"

###可失敗構(gòu)造器的傳播

類,結(jié)構(gòu)體或枚舉的可失敗構(gòu)造器可以橫向代理給同一個(gè)的類刷允,結(jié)構(gòu)體或枚舉中的另一個(gè)可失敗構(gòu)造器冤留。類似的,子類的可失敗構(gòu)造器也能向上代理到超類的可失敗構(gòu)造器树灶。

不論發(fā)生何種情況纤怒,如果你代理給的另一個(gè)構(gòu)造器發(fā)生了構(gòu)造過程失敗,整個(gè)構(gòu)造過程程序立刻失敗天通,后續(xù)的構(gòu)造過程代碼也不會(huì)執(zhí)行泊窘。

可失敗構(gòu)造器也可代理給非可失敗構(gòu)造器。如果你想在一個(gè)已經(jīng)存在的不會(huì)失敗的構(gòu)造過程中添加一個(gè)可能失敗的狀況時(shí)可以用到這招

下面的例子定義了一個(gè)Product的子類CartItem像寒。CartItem類模擬了在線購(gòu)物車中的一件物品烘豹。CartItem包含一個(gè)叫做quantity的常量屬性并確保這個(gè)屬性總是不小于1:

class CartItem:Product{

let quantity:Int!

init?(name:String, quantity:Int) {

super.init(name: name)

if quantity <1{returnnil}

self.quantity = quantity

?}

}

quantity屬性類型為隱式解析整型(Int!)。就像Product類的name屬性一樣诺祸,這意味著quantity屬性在構(gòu)造過程中被賦值前有一個(gè)默認(rèn)值nil携悯。

CartItem的可失敗構(gòu)造器開始于向上代理到超類Product的init(name:)構(gòu)造器。這滿足了要求–可失敗構(gòu)造器必需總是在構(gòu)造過程失敗被觸發(fā)之前完成構(gòu)造器代理序臂。

如果超類的構(gòu)造過程由于空的name值而失敗蚌卤,整個(gè)構(gòu)造過程程序立刻失敗,后續(xù)的構(gòu)造過程代碼也不會(huì)執(zhí)行奥秆。如果超類構(gòu)造過程成功,CartItem構(gòu)造器會(huì)驗(yàn)證獲取到的quantity值是否大于等于1咸灿。

如果你用非空的name和大等于1的quantity創(chuàng)建一個(gè)CartItem實(shí)例构订,構(gòu)造過程會(huì)成功:

if let twoSocks =CartItem(name:"sock", quantity:2) {

println("Item:\(twoSocks.name), quantity:\(twoSocks.quantity)")

}

// prints "Item: sock, quantity: 2"

如果你試著用值為0的quantity創(chuàng)建一個(gè)CartItem實(shí)例,構(gòu)造器將會(huì)導(dǎo)致構(gòu)造過程失敱苁浮:

if let zeroShirts =CartItem(name:"shirt", quantity:0) {

println("Item:\(zeroShirts.name), quantity:\(zeroShirts.quantity)")

}else{

println("Unable to initialize zero shirts")

}

// prints "Unable to initialize zero shirts"

同樣地悼瘾,如果你嘗試用空的name值創(chuàng)建一個(gè)CartItem實(shí)例,超類Product構(gòu)造器會(huì)導(dǎo)致構(gòu)造過程失斏笮亍:

if let oneUnnamed =CartItem(name:"", quantity:1) {

println("Item:\(oneUnnamed.name), quantity:\(oneUnnamed.quantity)")

}else{

println("Unable to initialize one unnamed product")

}

// prints "Unable to initialize one unnamed product"

###可失敗構(gòu)造器的重載

你可以在子類中重載超類的可失敗構(gòu)造器亥宿,就像重載其他任意構(gòu)造器那樣。另外砂沛,你還可以用子類的非可失敗構(gòu)造器重載超類的可失敗構(gòu)造器烫扼。這讓你能定義一個(gè)構(gòu)造過程不能失敗的子類,盡管超類的構(gòu)造過程是允許失敗的碍庵。

注意如果你用子類的非可失敗構(gòu)造器重載了超類的可失敗構(gòu)造器映企,子類構(gòu)造器不能向上代理到超類構(gòu)造器悟狱。非可失敗構(gòu)造器庸官都不能代理給可失敗構(gòu)造器。

注意:你可以用非可失敗構(gòu)造器重載可失敗構(gòu)造器堰氓,但是不能倒過來(lái)

下面的例子定義了一個(gè)叫做Document的類挤渐。這個(gè)類模擬了一個(gè)用name屬性初始化的文檔,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 non-empty name value

init?(name:String) {

ifname.isEmpty {returnnil}

self.name = name

?}

}

下一個(gè)例子定義了一個(gè)Document的子類AutomaticallyNamedDocument浴麻。子類AutomaticallyNamedDocument重載了兩個(gè)Document添加的指定構(gòu)造器。這些重載確保AutomaticallyNamedDocument實(shí)例在不用name初始化或傳入init(name:)的值是空字符串時(shí)將“[Untitled]”作為name的初值:

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:)構(gòu)造器重載了它超類的可失敗的init?(name:)構(gòu)造器囤攀。因?yàn)锳utomaticallyNamedDocument應(yīng)付空字符串情形的方式與它的超類不同白胀,它的構(gòu)造器不需要失敗,所以它提供了一個(gè)非可失敗版本的構(gòu)造器來(lái)替代抚岗。

###init!可失敗構(gòu)造器

你一貫地通過在init關(guān)鍵字后放置問號(hào)(init?)來(lái)定義一個(gè)創(chuàng)建可選值類型實(shí)例的可失敗構(gòu)造器或杠。另外,你還可以定義一個(gè)創(chuàng)建隱式解析可選類型實(shí)例的可失敗構(gòu)造器宣蔚。在init關(guān)鍵字后放置一個(gè)嘆號(hào)來(lái)替代問號(hào)(init!)就可以啦向抢。

你可以從init?代理到init!,反之亦然胚委;你可以用init!重載init?挟鸠,反之亦然。你也可以從init代理到init!亩冬,不過這么做將會(huì)在init!發(fā)生構(gòu)造過程失敗時(shí)觸發(fā)一個(gè)斷言艘希。

##必需構(gòu)造器(Required Initializers)

當(dāng)你想讓一個(gè)類的某個(gè)構(gòu)造器被所有子類都實(shí)現(xiàn),你可以在定義這個(gè)構(gòu)造器時(shí)在前面用required修飾:

class SomeClass{

required init() {

// initializer implementation goes here

?}

}

你也必須在它的子類定義必需構(gòu)造器前用required修飾硅急,但不必用override修飾:

class SomeSubClass:SomeClass{

requiredinit() {

// subclass implementation of the required initializer goes here

?}

}

這樣的語(yǔ)法可以將這種“必需”信號(hào)傳遞給未來(lái)更多的子類覆享,表明這個(gè)構(gòu)造器一定要實(shí)現(xiàn),千秋萬(wàn)代~

當(dāng)然你可以滿足構(gòu)造器的繼承規(guī)則來(lái)繼承必需構(gòu)造器营袜,這樣就不用“必須”重寫“必需”構(gòu)造器了:

class SomeSubSub Class:SomeSubClass{

convenience init(a:String) {

self.init()

//subsubclass implementation of a convenience initializer goes here

?}

}

##通過閉包和函數(shù)來(lái)設(shè)置屬性的默認(rèn)值

如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要特別的定制或準(zhǔn)備撒顿,你就可以使用閉包或全局函數(shù)來(lái)為其屬性提供定制的默認(rèn)值。每當(dāng)某個(gè)屬性所屬的新類型實(shí)例創(chuàng)建時(shí)荚板,對(duì)應(yīng)的閉包或函數(shù)會(huì)被調(diào)用凤壁,而它們的返回值會(huì)當(dāng)做默認(rèn)值賦值給這個(gè)屬性。

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)于是將閉包本身作為值賦值給了屬性免绿,而不是將閉包的返回值賦值給屬性唧席。

如果你使用閉包來(lái)初始化屬性的值,請(qǐng)記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒有初始化袱吆。這意味著你不能夠在閉包里訪問其它的屬性厌衙,就算這個(gè)屬性有默認(rèn)值也不允許。同樣绞绒,你也不能使用隱式的self屬性婶希,或者調(diào)用其它的實(shí)例方法。

原文鏈接:http://yulingtianxia.com/blog/2014/06/24/initialization-in-swift/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蓬衡,一起剝皮案震驚了整個(gè)濱河市喻杈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狰晚,老刑警劉巖筒饰,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壁晒,居然都是意外死亡瓷们,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門秒咐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谬晕,“玉大人,你說我怎么就攤上這事携取≡芮” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵雷滋,是天一觀的道長(zhǎng)不撑。 經(jīng)常有香客問我,道長(zhǎng)晤斩,這世上最難降的妖魔是什么焕檬? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮尸昧,結(jié)果婚禮上揩页,老公的妹妹穿的比我還像新娘。我一直安慰自己烹俗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布萍程。 她就那樣靜靜地躺著幢妄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茫负。 梳的紋絲不亂的頭發(fā)上蕉鸳,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼潮尝。 笑死榕吼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的勉失。 我是一名探鬼主播羹蚣,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼乱凿!你這毒婦竟也來(lái)了顽素?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤徒蟆,失蹤者是張志新(化名)和其女友劉穎胁出,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體段审,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡全蝶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寺枉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抑淫。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖型凳,靈堂內(nèi)的尸體忽然破棺而出丈冬,到底是詐尸還是另有隱情,我是刑警寧澤甘畅,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布埂蕊,位于F島的核電站,受9級(jí)特大地震影響疏唾,放射性物質(zhì)發(fā)生泄漏蓄氧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一槐脏、第九天 我趴在偏房一處隱蔽的房頂上張望喉童。 院中可真熱鬧,春花似錦顿天、人聲如沸堂氯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咽白。三九已至,卻和暖如春鸟缕,著一層夾襖步出監(jiān)牢的瞬間晶框,已是汗流浹背排抬。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留授段,地道東北人蹲蒲。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像侵贵,于是被迫代替她去往敵國(guó)和親届搁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 123.繼承 一個(gè)類可以從另外一個(gè)類繼承方法,屬性和其他特征模燥。當(dāng)一個(gè)類繼承另外一個(gè)類時(shí), 繼承類叫子類, 被繼承的...
    無(wú)灃閱讀 1,383評(píng)論 2 4
  • 本章將會(huì)介紹 存儲(chǔ)屬性的初始賦值自定義構(gòu)造過程默認(rèn)構(gòu)造器值類型的構(gòu)造器代理類的繼承和構(gòu)造過程可失敗構(gòu)造器必要構(gòu)造器...
    寒橋閱讀 769評(píng)論 0 0
  • 構(gòu)造過程是使用類蔫骂、結(jié)構(gòu)體或枚舉類型的實(shí)例之前的準(zhǔn)備過程么翰。在新實(shí)例可用前必須執(zhí)行這個(gè)過程,具體操作包括設(shè)置實(shí)例中每個(gè)...
    莽原奔馬668閱讀 681評(píng)論 0 3
  • 下標(biāo)腳本 下標(biāo)腳本 可以定義在類辽旋、結(jié)構(gòu)體和枚舉這些目標(biāo)中浩嫌,可以認(rèn)為是訪問集合(collection),列表(li...
    cht005288閱讀 444評(píng)論 0 0
  • Category (category也可以叫做分類,類別或者類目) category可以(在我們不知道某個(gè)類的內(nèi)部...
    大布溜閱讀 549評(píng)論 0 0