Swift 中关于构造器的猜测

19 Oct 2017 by 晓晨


全文都是猜的。全文省略“我觉得”、“我猜”等词。

Swift 中关于属性的猜测,我只是想用自己的话表述一下相关概念,其中有自己的猜想,但是我的猜想是符合我理解的文档中的各种规则的。

Swift 为构造器指定各种规则,目的就是确保所有的属性都被正确初始化,这里所说的正确初始化可能包括下面这些要求,

Swift 制定了各种规则,如下,

定义 designated 和 convenience 构造器及其相互之间的调用规则(rules for delegation calls between initializers)

引申规则

这些规则都是由上述规则引申的,遵循下面的规则是被迫的,只是为了遵守上面的规则。

两阶段初始化(two-phase initialization)

第一阶段,初始化所有的 stored 属性,包括父类的,不管是不是想要的结果,在该阶段完成时,所有的 stored 属性都有初始值了。

此时,可以通过 self 访问属性和方法了,相当于这个实例已经可以用了。

第二阶段,做的事情和普通实例方法没区别,只是把整个实例调整到一个可以用的样子。

(调用当前类的 convenience init -> )调用当前类的 designated init -> 初始化当前类中的属性 -> 调用父类的 designated init -> 初始化父类中的属性 -> 调用父类的父类的 designated init -> 初始化父类的父类的所有属性

-> 两阶段分割点 ->

父类的父类的 designated init 中初始化所有属性之后的部分 -> 父类的 designated init 中初始化所有属性之后的部分 -> 当前类中 designated init 中初始化所有属性之后的部分( -> 当前类 convenience init 中调用 designated init 之后的部分)

保证二阶段初始化的是四条安全检查(safety checks),它们最终保证了要求一和要求三。

不完善的描述

上面的描述有一点不完善的地方,注意下面两条规则中的区别,

A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property.

A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class).

这个区别的结果就是,在 designated 构造器中,在 delegate up 父类 designated 构造器前,在当前类引入的所有属性初始化之后,可以访问当前类的属性(不能是继承属性,因为还没初始化)。

class C {}

class D: C {
    let i = 0
    override init() {
        print(i)
        super.init()
    }
}

也就是说,上面对两阶段初始化的描述中,分割点之前也可能出现一部分对当前属性的访问,但这并没有违背要求三,因为,此时访问的属性已经初始化了,所以没有在初始化前访问;父类不会修改当前类的属性,所以不用担心父类的重写。

通常情况下,我们没有必要把对当前类引入的属性的修改放在其初始化之后、调用父类构造器之前,我想到一个需要这样做的场景,如下,

class C {
    init?() { /* expensive things */ }
}

class D: C {
    let i = 0
    override init?() {
        if i == 0 {
            return nil
        }
        super.init()
    }
}

如果子类构造器是 failable 的,在调用父类构造器前就及时中止能够减少调用父类构造器的开销。不过我觉得除非必要,就别放这么奇怪的位置。

自动构造器继承的规则

Swift 默认是不继承构造器的,为了防止继承的构造器不能初始化子类引入的属性。但当符合一定要求时,会有自动构造器继承这个机制。这个机制本身是为了省事的,它所遵循的规则保证了构造器继承是安全的。

自动构造器继承的大前提是当前类引入的新属性都有提供默认值。

第一条规则是关于 designated 构造器的自动继承,只要子类中定义了 designated 构造器,就不会有自动构造器继承。不管子类中的这个 designated 构造器是直接写的、或是覆盖的父类的构造器(说覆盖只能说覆盖的是父类的 designated 构造器,convenience 构造器不能覆盖)、或是写与父类中 convenience 构造器同名的 designated 构造器(这不叫覆盖)。

第二条规则是关于 convenience 构造器的自动继承,是独立于第一条的,只要父类的所有 designated 构造器在子类中都有(不管是自动继承或是覆盖),就自动继承父类的所有 convenience 构造器。

大前提确保所有属性都被初始化。

第一条规则确保我想在 designated 构造器中做的不会被继承的 designated 构造器跳过。

第二条规则确保我想在 designated 构造器中做的不会被继承的 convenience 构造器跳过。

第一条规则符合 Swift 在很多情况下的做法,要么自动帮你做符合你预期的事情,但只要你决定自己来,那就完全交给你做,如果你不写 designated 构造器,那我就帮你继承下来父类的所有 designated 构造器,但只要你自己写了,决定自己来,那我就完全不管了,一个 designated 也不帮你继承。

第二条规则保证 convenience 构造器总是会调用当前类的 designated 构造器,从而符合构造器相互之间调用的规则。


怎样给一个类写构造器?