1、自定义初始化方法要先调用自己类默认初始化方法,自己重写默认初始化方法要先调用父类默认初始化方法
2、应该要先调用父类的构造器或者自身的默认构造器,以防止先给属性赋值了然后才调用父类或者自身的默认构造器把以前的赋值覆盖了
一个类的所有存储属性-包括从父类继承而来的属性-都必须在初始化的时候设置初始值。
Swift为class类型定义了两种构造器来确保它们所有的存储属性都设置了初始值。这两种方式叫做指定构造器和便捷构造器。
指定构造器和便捷构造器
指定构造器是一个类最主要的构造器。指定构造器通过设置所有属性的初值并且调用所有的父类构造器来根据构造链一次初始化所有的属性。
类所拥有的指定构造器很少,一般只有一个,并且是连接这父类的构造链依次完成构造的。
每个类至少有一个指定构造器,在有些情况下,需要使用继承来从父类中得到该指定构造器,更多内容可以查看后面的Automatic Initializer Inheritance章节。
便捷构造器是类的第二种常用构造器。你可以调用同一个类中的指定构造器来定义一个便捷构造器,使用指定构造器来设置相关的参数默认值。你还可以定义一个便捷构造器来创建这个类的实例或者是别的特殊用途。
如果你的类不需要它们,也可以不定义便捷构造器。不过对于常见初始化模型需要快捷方式的时候创建一个便捷构造器可以让你的初始化过程变成十分简单便捷。
构造链
为了简化指定构造器和便捷构造器的关系,Swift为两种构造器的代理调用设置了三个规则:
规则1
指定构造器必须调用它直接父类的指定构造器
规则2
便捷构造器只能调用同一个类中的其它构造器
规则3
便捷构造器必须以调用一个指定构造器结束
记下这些规则的简单方法是:
指定构造器必须向上代理
便捷构造器必须横向代理
可以使用下面的图来表示:
父类中的两个便捷构造器依次调用直到指定构造器,子类中的指定构造器调用了父类的指定构造器。
注意:这些规则不会影响每个类的实例创建过程。每个构造器都可以用来创建它们各自的类的实例。这些规则只影响你如何编写类实现代码。
下图演示的是另一种更为复杂的具有四个等级的类。这个图展示了指定构造器在类的初始化过程中如何被作为“漏斗”节点的。这个构造链简化了类与类之间的交互关系:
image
两阶段的初始化
在Swift中,类的初始化要经过两个阶段。在第一个阶段,每一个存储属性都被设置了一个初始值。一旦每个存储属性的值在初始化阶段被设置了,在第二个阶段,每个类在这个实例被使用之前都会有机会来设置它们相应的存储属性。
两阶段的模式使初始化过程更加安全,还可以让每个类在类的层级关系中具有更多的可能性。两阶段初始化方法可以防止属性在被初始化之前就被使用,或者是被另一个构造器错误地赋值。
注意:Swift的这种两阶段初始化方法跟Objective-C中的类似。主要的差别是在第一个过程中,Objective-C为每个属性赋值0或者null,而在Swift中,可以个性化设置这些初始值,还可以处理一些初始值不能是0或者nil的情况。
Swift编译器通过四重检查来确保两阶段式的初始化过程是完全正确无误的:
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:
image
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.
构造器的继承和重写
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.
构造器自动继承
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.
指定初始化和便捷初始化的语法
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
}
指定初始化和便捷初始化实战
下面的例子演示的是指定构造器,便捷构造器和自动构造器继承的实战。例子中定义了三个类分别叫Food,RecipeIngredient和ShoppingListItem,并给出了他们的继承关系。
基类叫做Food,是一个简单的类只有一个name属性:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
image
类不存在成员逐一构造器,所以Food类提供了一个指定构造器,使用参数name来完成初始化:
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
init(name:String)构造器就是Food类中的指定构造器,因为它保证了每一个Food实例的属性都被初始化了。由于它没有父类,所以不需要调用super.init()构造器。
Food类也提供了便捷构造器init(),这个构造器没有参数,仅仅只是将name设置为了[Unnamed]:
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
下一个类是Food的子类,叫做RecipeIngredient。这个类描述的是做饭时候的配料,包括一个数量属性Int类型,然后定义了两个构造器:
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)
}
}
image
RecipeIngredient类有它自己的指定构造器init(name: String, quantity:Int),用来创建一个新的RecipeIngredient实例。在这个指定构造器中它调用了父类的指定构造器init(name:String)。然后它还有一个便捷构造器,init(name),它使用了同一个类中的指定构造器。当然它还包括一个继承来的默认构造器init(),这个构造器将使用RecipeIngredient中的init(name: String)构造器。
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.
上述三种构造器都可以用来创建RecipeIngredient实例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
最后一个类是ShoppingListItem继承自RecipeIngredient,它又包括了另外两个属性,是否已购买purchased,描述description,描述本身还是一个计算属性:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name.lowercaseString)"
output += purchased ? " yes" : " no"
return output
}
}
注意:ShoppingListItem没有定义构造器来初始化purchased的值,因为每个商品在买之前purchased都是默认被设置为没有被购买的。
因为ShoppingListItem没有提供其他构造器,那么它就完全继承了父类的构造器,用下图可以说明:
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
通过输出可以看出所有的实例在创建的时候,属性的默认值都被正确的初始化了。
网友评论