类类型的构造器代理
Swift构造器需遵循以下三大规则:
- 指定构造器必须调用它直接父类的指定构造器方法
- 便利构造器必须调用同一个类中定义的其他初始化方法
- 便利构造器在最后必须调用一个指定构造器
两段式构造过程
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
Swift提供了4种安全检查是开发者必须遵循的。
- 若子类中定义了父类没有的变量,则在子类的指定构造器中需先对该变量赋值,再调用父类的构造器。
- 若要在子类的指定构造器中修改继承来的变量的值,则需先调用父类的构造器。
- 若要在子类的便利构造器中修改任意变量的值,则需先调用同类的构造器。
- 所有属性被赋初始值后(即第一阶段结束后)才能调用实例方法。
阶段 1
类的某个指定构造器或便利构造器被调用。
完成类的新实例内存的分配,但此时内存还没有被初始化。
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2
从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self
、修改它的属性并调用实例方法等等。
最终,继承链中任意的便利构造器有机会自定义实例和使用 self
。
构造器的继承和重写
规则:
- 若定义的子类构造器与父类指定构造器相匹配,则需要在定义子类构造器时加上
override
修饰符。 - 若定义的子类构造器与父类便利构造器相匹配,不需要加
override
修饰符。 - 若父类的指定构造器无参数,且在子类构造器中没有自定义父类的属性,那么在子类指定构造器中可以省略
super.init()
import UIKit
class lei{
var name: String?
init(){
self.name = "123"
}
}
class sublei: lei{
var color: String
init(color: String) {
self.color = color
//隐式调用super.init()
}
}
构造器的自动继承
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则将适用:
规则 1: 如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
import UIKit
class lei{
var name: String?
init(_ name: String){
self.name = name
}
}
class sublei: lei{
var color = "123"
}
let a = sublei("abc")
规则 2: 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。 即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
注意: 子类可以将父类的指定构造器实现为便利构造器来满足规则 2。
import UIKit
class lei{
var name: String?
init(_ name: String){
self.name = name
}
convenience init(){
self.init("abc")
}
}
class sublei: lei{
var color: String?
init(_ name: String, _ color: String?){
self.color = color
super.init(name)
}
convenience override init(_ name: String) {
self.init(name, nil)
}
}
let one = sublei()
可失败构造器的重写与继承
语法:init?()
可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过 return nil
语句来表明可失败构造器在何种情况下应该 “失败”。
严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用 return nil
表明可失败构造器构造失败,而不要用关键字 return
来表明构造成功。
重写可失败构造器可以在子类中定义一个非可失败构造器。
注意:
可以用非可失败构造器重写可失败构造器,但反过来却不行。
import UIKit
class lei{
var name: String?
init(){}
init?(_ name: String){
if name.isEmpty { return nil }
self.name = name
}
}
class sublei: lei{
override init() {
super.init()
self.name = "abc"
}
override init(_ name: String) {
super.init()
if name.isEmpty{
self.name = "abc"
}else{
self.name = name
}
}
}
你可以在子类的不可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的 suble子类的 name 属性的值总是 "abc",它在构造过程中使用了父类的可失败构造器 init?(name:):
class sublei: lei {
override init() {
super.init(name: "abc")!
}
}
在这个例子中,如果在调用父类的可失败构造器 init?(name:) 时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。
必要构造器
在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 构造器的实现代码
}
}
在子类重写父类的必要构造器时,必须在子类的构造器前也添加 required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加 override修饰符:
class SomeSubclass: SomeClass {
required init() {
// 构造器的实现代码
}
}