1、自定義初始化方法要先調(diào)用自己類默認(rèn)初始化方法,自己重寫默認(rèn)初始化方法要先調(diào)用父類默認(rèn)初始化方法
2谎脯、應(yīng)該要先調(diào)用父類的構(gòu)造器或者自身的默認(rèn)構(gòu)造器恬汁,以防止先給屬性賦值了然后才調(diào)用父類或者自身的默認(rèn)構(gòu)造器把以前的賦值覆蓋了
一個(gè)類的所有存儲(chǔ)屬性-包括從父類繼承而來(lái)的屬性-都必須在初始化的時(shí)候設(shè)置初始值。
Swift為class類型定義了兩種構(gòu)造器來(lái)確保它們所有的存儲(chǔ)屬性都設(shè)置了初始值许溅。這兩種方式叫做指定構(gòu)造器和便捷構(gòu)造器。
指定構(gòu)造器和便捷構(gòu)造器
指定構(gòu)造器是一個(gè)類最主要的構(gòu)造器。指定構(gòu)造器通過(guò)設(shè)置所有屬性的初值并且調(diào)用所有的父類構(gòu)造器來(lái)根據(jù)構(gòu)造鏈一次初始化所有的屬性葫笼。
類所擁有的指定構(gòu)造器很少,一般只有一個(gè)拗馒,并且是連接這父類的構(gòu)造鏈依次完成構(gòu)造的路星。
每個(gè)類至少有一個(gè)指定構(gòu)造器,在有些情況下诱桂,需要使用繼承來(lái)從父類中得到該指定構(gòu)造器洋丐,更多內(nèi)容可以查看后面的Automatic Initializer Inheritance章節(jié)。
便捷構(gòu)造器是類的第二種常用構(gòu)造器挥等。你可以調(diào)用同一個(gè)類中的指定構(gòu)造器來(lái)定義一個(gè)便捷構(gòu)造器友绝,使用指定構(gòu)造器來(lái)設(shè)置相關(guān)的參數(shù)默認(rèn)值。你還可以定義一個(gè)便捷構(gòu)造器來(lái)創(chuàng)建這個(gè)類的實(shí)例或者是別的特殊用途肝劲。
如果你的類不需要它們迁客,也可以不定義便捷構(gòu)造器。不過(guò)對(duì)于常見(jiàn)初始化模型需要快捷方式的時(shí)候創(chuàng)建一個(gè)便捷構(gòu)造器可以讓你的初始化過(guò)程變成十分簡(jiǎn)單便捷辞槐。
構(gòu)造鏈
為了簡(jiǎn)化指定構(gòu)造器和便捷構(gòu)造器的關(guān)系掷漱,Swift為兩種構(gòu)造器的代理調(diào)用設(shè)置了三個(gè)規(guī)則:
規(guī)則1
指定構(gòu)造器必須調(diào)用它直接父類的指定構(gòu)造器
規(guī)則2
便捷構(gòu)造器只能調(diào)用同一個(gè)類中的其它構(gòu)造器
規(guī)則3
便捷構(gòu)造器必須以調(diào)用一個(gè)指定構(gòu)造器結(jié)束
記下這些規(guī)則的簡(jiǎn)單方法是:
指定構(gòu)造器必須向上代理
便捷構(gòu)造器必須橫向代理
可以使用下面的圖來(lái)表示:
父類中的兩個(gè)便捷構(gòu)造器依次調(diào)用直到指定構(gòu)造器,子類中的指定構(gòu)造器調(diào)用了父類的指定構(gòu)造器榄檬。
注意:這些規(guī)則不會(huì)影響每個(gè)類的實(shí)例創(chuàng)建過(guò)程卜范。每個(gè)構(gòu)造器都可以用來(lái)創(chuàng)建它們各自的類的實(shí)例。這些規(guī)則只影響你如何編寫類實(shí)現(xiàn)代碼鹿榜。
下圖演示的是另一種更為復(fù)雜的具有四個(gè)等級(jí)的類海雪。這個(gè)圖展示了指定構(gòu)造器在類的初始化過(guò)程中如何被作為“漏斗”節(jié)點(diǎn)的。這個(gè)構(gòu)造鏈簡(jiǎn)化了類與類之間的交互關(guān)系:
兩階段的初始化
在Swift中犬缨,類的初始化要經(jīng)過(guò)兩個(gè)階段喳魏。在第一個(gè)階段,每一個(gè)存儲(chǔ)屬性都被設(shè)置了一個(gè)初始值怀薛。一旦每個(gè)存儲(chǔ)屬性的值在初始化階段被設(shè)置了刺彩,在第二個(gè)階段,每個(gè)類在這個(gè)實(shí)例被使用之前都會(huì)有機(jī)會(huì)來(lái)設(shè)置它們相應(yīng)的存儲(chǔ)屬性。
兩階段的模式使初始化過(guò)程更加安全创倔,還可以讓每個(gè)類在類的層級(jí)關(guān)系中具有更多的可能性嗡害。兩階段初始化方法可以防止屬性在被初始化之前就被使用,或者是被另一個(gè)構(gòu)造器錯(cuò)誤地賦值畦攘。
注意:Swift的這種兩階段初始化方法跟Objective-C中的類似霸妹。主要的差別是在第一個(gè)過(guò)程中,Objective-C為每個(gè)屬性賦值0或者null知押,而在Swift中叹螟,可以個(gè)性化設(shè)置這些初始值,還可以處理一些初始值不能是0或者nil的情況台盯。
Swift編譯器通過(guò)四重檢查來(lái)確保兩階段式的初始化過(guò)程是完全正確無(wú)誤的:
Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all its own properties are initialized before it hands off up the chain.
Safety check 2
A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
Safety check 3
A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.
Safety check 4
An initializer cannot call any instance methods, read the values of any instance properties, or refer toself
as a value until after the first phase of initialization is complete.
The class instance is not fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.
Here’s how two-phase initialization plays out, based on the four safety checks above:
Phase 1
A designated or convenience initializer is called on a class.
Memory for a new instance of that class is allocated. The memory is not yet initialized.
A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.
The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
This continues up the class inheritance chain until the top of the chain is reached.
Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.
Phase 2
Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self
and can modify its properties, call its instance methods, and so on.
Finally, any convenience initializers in the chain have the option to customize the instance and to work with self
.
Here’s how phase 1 looks for an initialization call for a hypothetical subclass and superclass:
In this example, initialization begins with a call to a convenience initializer on the subclass. This convenience initializer cannot yet modify any properties. It delegates across to a designated initializer from the same class.
The designated initializer makes sure that all of the subclass’s properties have a value, as per safety check 1. It then calls a designated initializer on its superclass to continue the initialization up the chain.
The superclass’s designated initializer makes sure that all of the superclass properties have a value. There are no further superclasses to initialize, and so no further delegation is needed.
As soon as all properties of the superclass have an initial value, its memory is considered fully initialized, and Phase 1 is complete.
Here’s how phase 2 looks for the same initialization call:
The superclass’s designated initializer now has an opportunity to customize the instance further (although it does not have to).
Once the superclass’s designated initializer is finished, the subclass’s designated initializer can perform additional customization (although again, it does not have to).
Finally, once the subclass’s designated initializer is finished, the convenience initializer that was originally called can perform additional customization.
構(gòu)造器的繼承和重寫
Unlike subclasses in Objective-C, Swift subclasses do not not inherit their superclass initializers by default. Swift’s approach prevents a situation in which a simple initializer from a superclass is automatically inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.
If you want your custom subclass to present one or more of the same initializers as its superclass—perhaps to perform some customization during initialization—you can provide an overriding implementation of the same initializer within your custom subclass.
If the initializer you are overriding is a designated initializer, you can override its implementation in your subclass and call the superclass version of the initializer from within your overriding version.
If the initializer you are overriding is a convenience initializer, your override must call another designated initializer from its own subclass, as per the rules described above in Initializer Chaining.
NOTE
Unlike methods, properties, and subscripts, you do not need to write the override
keyword when overriding an initializer.
構(gòu)造器自動(dòng)繼承
As mentioned above, subclasses do not not inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met. In practice, this means that you do not need to write initializer overrides in many common scenarios, and can inherit your superclass initializers with minimal effort whenever it is safe to do so.
Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
These rules apply even if your subclass adds further convenience initializers.
NOTE
A subclass can implement a superclass designated initializer as a subclass convenience initializer as part of satisfying rule 2.
指定初始化和便捷初始化的語(yǔ)法
Designated initializers for classes are written in the same way as simple initializers for value types:
init(parameters) {
statements
}
Convenience initializers are written in the same style, but with the convenience
keyword placed before the init
keyword, separated by a space:
convenience init(parameters) {
statements
}
指定初始化和便捷初始化實(shí)戰(zhàn)
下面的例子演示的是指定構(gòu)造器罢绽,便捷構(gòu)造器和自動(dòng)構(gòu)造器繼承的實(shí)戰(zhàn)。例子中定義了三個(gè)類分別叫Food静盅,RecipeIngredient和ShoppingListItem良价,并給出了他們的繼承關(guān)系。
基類叫做Food蒿叠,是一個(gè)簡(jiǎn)單的類只有一個(gè)name屬性:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
類不存在成員逐一構(gòu)造器明垢,所以Food類提供了一個(gè)指定構(gòu)造器,使用參數(shù)name來(lái)完成初始化:
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
init(name:String)構(gòu)造器就是Food類中的指定構(gòu)造器市咽,因?yàn)樗WC了每一個(gè)Food實(shí)例的屬性都被初始化了痊银。由于它沒(méi)有父類,所以不需要調(diào)用super.init()構(gòu)造器魂务。
Food類也提供了便捷構(gòu)造器init()曼验,這個(gè)構(gòu)造器沒(méi)有參數(shù)泌射,僅僅只是將name設(shè)置為了[Unnamed]:
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
下一個(gè)類是Food的子類粘姜,叫做RecipeIngredient。這個(gè)類描述的是做飯時(shí)候的配料熔酷,包括一個(gè)數(shù)量屬性Int類型孤紧,然后定義了兩個(gè)構(gòu)造器:
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
RecipeIngredient類有它自己的指定構(gòu)造器init(name: String, quantity:Int),用來(lái)創(chuàng)建一個(gè)新的RecipeIngredient實(shí)例拒秘。在這個(gè)指定構(gòu)造器中它調(diào)用了父類的指定構(gòu)造器init(name:String)号显。然后它還有一個(gè)便捷構(gòu)造器,init(name)躺酒,它使用了同一個(gè)類中的指定構(gòu)造器押蚤。當(dāng)然它還包括一個(gè)繼承來(lái)的默認(rèn)構(gòu)造器init(),這個(gè)構(gòu)造器將使用RecipeIngredient中的init(name: String)構(gòu)造器羹应。
RecipeIngredient
also defines a convenience initializer, init(name: String)
, which is used to create aRecipeIngredient
instance by name alone. This convenience initializer assumes a quantity of 1
for anyRecipeIngredient
instance that is created without an explicit quantity. The definition of this convenience initializer makes RecipeIngredient
instances quicker and more convenient to create, and avoids code duplication when creating several single-quantity RecipeIngredient
instances. This convenience initializer simply delegates across to the class’s designated initializer.
Note that the init(name: String)
convenience initializer provided by RecipeIngredient
takes the same parameters as the init(name: String)
designated initializer from Food
. Even though RecipeIngredient
provides this initializer as a convenience initializer, RecipeIngredient
has nonetheless provided an implementation of all of its superclass’s designated initializers. Therefore, RecipeIngredient
automatically inherits all of its superclass’s convenience initializers too.
In this example, the superclass for RecipeIngredient
is Food
, which has a single convenience initializer calledinit()
. This initializer is therefore inherited by RecipeIngredient
. The inherited version of init()
functions in exactly the same way as the Food
version, except that it delegates to theRecipeIngredient
version of init(name: String)
rather than the Food
version.
上述三種構(gòu)造器都可以用來(lái)創(chuàng)建RecipeIngredient實(shí)例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
最后一個(gè)類是ShoppingListItem繼承自RecipeIngredient揽碘,它又包括了另外兩個(gè)屬性,是否已購(gòu)買purchased,描述description雳刺,描述本身還是一個(gè)計(jì)算屬性:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name.lowercaseString)"
output += purchased ? " yes" : " no"
return output
}
}
注意:ShoppingListItem沒(méi)有定義構(gòu)造器來(lái)初始化purchased的值劫灶,因?yàn)槊總€(gè)商品在買之前purchased都是默認(rèn)被設(shè)置為沒(méi)有被購(gòu)買的。
因?yàn)镾hoppingListItem沒(méi)有提供其他構(gòu)造器掖桦,那么它就完全繼承了父類的構(gòu)造器本昏,用下圖可以說(shuō)明:
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 yes
// 1 x bacon no
// 6 x eggs no
通過(guò)輸出可以看出所有的實(shí)例在創(chuàng)建的時(shí)候,屬性的默認(rèn)值都被正確的初始化了枪汪。