簡(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/