指定构造器和便捷构造器

Swift为class类型定义了两种构造器来确保它们所有的存储属性都设置了初始值。这两种方式叫做指定构造器和便捷构造器。

 

指定构造器和便捷构造器

 

指定构造器是一个类最主要的构造器。指定构造器通过设置所有属性的初值并且调用所有的父类构造器来根据构造链一次初始化所有的属性。

 

类所拥有的指定构造器很少,一般只有一个,并且是连接这父类的构造链依次完成构造的。

 

每个类至少有一个指定构造器,在有些情况下,需要使用继承来从父类中得到该指定构造器,更多内容可以查看后面的Automatic Initializer Inheritance章节。

 

便捷构造器是类的第二种常用构造器。你可以调用同一个类中的指定构造器来定义一个便捷构造器,使用指定构造器来设置相关的参数默认值。你还可以定义一个便捷构造器来创建这个类的实例或者是别的特殊用途。

 

如果你的类不需要它们,也可以不定义便捷构造器。不过对于常见初始化模型需要快捷方式的时候创建一个便捷构造器可以让你的初始化过程变成十分简单便捷。

 

构造链

 

为了简化指定构造器和便捷构造器的关系,Swift为两种构造器的代理调用设置了三个规则:

 

规则1

 

指定构造器必须调用它直接父类的指定构造器

 

规则2

 

便捷构造器只能调用同一个类中的其它构造器

 

规则3

 

便捷构造器必须以调用一个指定构造器结束

 

记下这些规则的简单方法是:

 

指定构造器必须向上代理

 

便捷构造器必须横向代理

 

可以使用下面的图来表示:

 

image

 

父类中的两个便捷构造器依次调用直到指定构造器,子类中的指定构造器调用了父类的指定构造器。

 

注意:这些规则不会影响每个类的实例创建过程。每个构造器都可以用来创建它们各自的类的实例。这些规则只影响你如何编写类实现代码。

 

下图演示的是另一种更为复杂的具有四个等级的类。这个图展示了指定构造器在类的初始化过程中如何被作为“漏斗”节点的。这个构造链简化了类与类之间的交互关系:

 

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 to  selfas 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:

 

image

 

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:

Java代码   收藏代码
  1. init(parameters) {  
  2.     statements  
  3. }  

 

Convenience initializers are written in the same style, but with the convenience keyword placed before the initkeyword, separated by a space:

Java代码   收藏代码
  1. convenience init(parameters) {  
  2.     statements  
  3. }  

 

指定初始化和便捷初始化实战

 

下面的例子演示的是指定构造器,便捷构造器和自动构造器继承的实战。例子中定义了三个类分别叫Food,RecipeIngredient和ShoppingListItem,并给出了他们的继承关系。

 

基类叫做Food,是一个简单的类只有一个name属性:

Java代码   收藏代码
  1. class Food {  
  2.     var name: String  
  3.     init(name: String) {  
  4.         self.name = name  
  5.     }  
  6.     convenience init() {  
  7.         self.init(name: "[Unnamed]")  
  8.     }  
  9. }  

 

下图就是Food类的构造链:

 

image

 

类不存在成员逐一构造器,所以Food类提供了一个指定构造器,使用参数name来完成初始化:

Java代码   收藏代码
  1. let namedMeat = Food(name: "Bacon")  
  2. // namedMeat's name is "Bacon"  

 

init(name:String)构造器就是Food类中的指定构造器,因为它保证了每一个Food实例的属性都被初始化了。由于它没有父类,所以不需要调用super.init()构造器。

 

Food类也提供了便捷构造器init(),这个构造器没有参数,仅仅只是将name设置为了[Unnamed]:

Java代码   收藏代码
  1. let mysteryMeat = Food()  
  2. // mysteryMeat's name is "[Unnamed]"  

 

下一个类是Food的子类,叫做RecipeIngredient。这个类描述的是做饭时候的配料,包括一个数量属性Int类型,然后定义了两个构造器:

Java代码   收藏代码
  1. class RecipeIngredient: Food {  
  2.     var quantity: Int  
  3.     init(name: String, quantity: Int) {  
  4.         self.quantity = quantity  
  5.         super.init(name: name)  
  6.     }  
  7.     convenience init(name: String) {  
  8.         self.init(name: name, quantity: 1)  
  9.     }  
  10. }  

 

下图表示这两个类的构造链:

 

imageRecipeIngredient类有它自己的指定构造器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 RecipeIngredientprovides 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 the RecipeIngredientversion of init(name: String) rather than the Food version.

 

上述三种构造器都可以用来创建RecipeIngredient实例:

Java代码   收藏代码
  1. let oneMysteryItem = RecipeIngredient()  
  2. let oneBacon = RecipeIngredient(name: "Bacon")  
  3. let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)  

 

最后一个类是ShoppingListItem继承自RecipeIngredient,它又包括了另外两个属性,是否已购买purchased,描述description,描述本身还是一个计算属性:

Java代码   收藏代码
  1. class ShoppingListItem: RecipeIngredient {  
  2.     var purchased = false  
  3.     var description: String {  
  4.     var output = "\(quantity) x \(name.lowercaseString)"  
  5.         output += purchased ? " yes" : " no"  
  6.         return output  
  7.     }  
  8. }  

 

注意:ShoppingListItem没有定义构造器来初始化purchased的值,因为每个商品在买之前purchased都是默认被设置为没有被购买的。

 

因为ShoppingListItem没有提供其他构造器,那么它就完全继承了父类的构造器,用下图可以说明:

 

image

你可以在创建ShoppingListItem实例时使用所有的继承构造器:

Java代码   收藏代码
  1. var breakfastList = [  
  2.     ShoppingListItem(),  
  3.     ShoppingListItem(name: "Bacon"),  
  4.     ShoppingListItem(name: "Eggs", quantity: 6),  
  5. ]  
  6. breakfastList[0].name = "Orange juice"  
  7. breakfastList[0].purchased = true  
  8. for item in breakfastList {  
  9.     println(item.description)  
  10. }  
  11. // 1 x orange juice yes  
  12. // 1 x bacon no  
  13. // 6 x eggs no  

 

通过输出可以看出所有的实例在创建的时候,属性的默认值都被正确的初始化了。

 

6、通过闭包或者函数来设置一个默认属性值

 

如果存储属性的默认值需要额外的特殊设置,可以使用闭包或者函数来完成。

 

闭包或者函数会创建一个临时变量来作为返回值为这个属性赋值。下面是如果使用闭包赋值的一个示意代码:

Java代码   收藏代码
  1. class SomeClass {  
  2.     let someProperty: SomeType = {  
  3.         // create a default value for someProperty inside this closure  
  4.         // someValue must be of the same type as SomeType  
  5.         return someValue  
  6.         }()  
  7. }  

 

需要注意的是在闭包结尾有两个小括号,告诉Swift这个闭包是需要立即执行的。

 

注意:如果你时候闭包来初始化一个属性,在闭包执行的时候,后续的一些属性还没有被初始化。在闭包中不要访问任何后面的属性,一面发生错误,也不能使用self属性,或者其它实例方法。

 

下面的例子是一个叫Checkerboard的结构,是由游戏Checkers来的

 

image这 个游戏是在一个10×10的黑白相间的格子上进行的。来表示这个游戏盘,使用了一个叫Checkerboard的结构,其中一个属性叫 boardColors,是一个100个Bool类型的数组。true表示这个格子是黑色,false表示是白色。那么在初始化的时候可以通过下面的代码 来初始化:

Java代码   收藏代码
  1. struct Checkerboard {  
  2.     let boardColors: Bool[] = {  
  3.         var temporaryBoard = Bool[]()  
  4.         var isBlack = false  
  5.         for i in 1...10 {  
  6.             for j in 1...10 {  
  7.                 temporaryBoard.append(isBlack)  
  8.                 isBlack = !isBlack  
  9.             }  
  10.             isBlack = !isBlack  
  11.         }  
  12.         return temporaryBoard  
  13.         }()  
  14.     func squareIsBlackAtRow(row: Int, column: Int) -> Bool {  
  15.         return boardColors[(row * 10) + column]  
  16.     }  
  17. }  

  

当一个新的Checkerboard实例创建的时候,闭包会执行,然后boardColor的默认值将会被依次计算并且返回,然后作为结构的一个属性。通过使用squareIsBlackAtRow工具函数可以检测是否被正确设置:

Java代码   收藏代码
  1. let board = Checkerboard()  
  2. println(board.squareIsBlackAtRow(0, column: 1))  
  3. // prints "true"  
  4. println(board.squareIsBlackAtRow(9, column: 9))  
  5. // prints "false"   

 

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)


原始链接http://letsswift.com/2014/06/initialization

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值