Swift的构造器的编写规则要比其它语言麻烦很多, 有很多限制, 有些限制也不是那么容易想明白的, 但我想Swift的设计者肯定是有自己的考虑,不会平白无故的去设置这些限制,在这篇文章中我将试着总结一下Swift语言在构造过程中的种种限制以及为什么会需要这些限制.

1: Swift会为构造器的每个参数自动生成一个跟内部名字相同的外部名

构造器并不像函数和方法那样在括号前有一个可辨别的名字(构造器统一叫init), 为了提高构造器的可读性就有了这条限制, 当然如果你实在是不想为构造器提供外部名,可以使用下划线_来显式描述它的外部名, 这样就不会有上述默认行为了.

2: 任何一个构造器都必须使当前对象内的非lazyOptional类型的存储属性获得确定值

如果你之前做过Java开发, 你可能会对这一点产生疑惑, 在Java的构造器里我们是不需要对所有的字段显示进行赋值的.
其实这个和Swift引入的Optional有关,在Swift中非Optional的变量默认是不会有值的, 除非你将它显示的定义为Optional的, 换句话说Swift中Optional类型的变量才相当于Java中的变量(java中的全局变量会自动被赋予默认值).而Swift中Optional类型的变量才会有默认值nil, 而且构造器的目的就是分配内存以及使当前对象处于一个确定的状态(属性都有值包括nil)

3: 如果结构体或类的所有的属性都有默认值, Swift才会为它们提供默认构造器

在java中如果你没有定义构造器, Java就会自动为你提供一个默认构造器, Swift为什么不?
基实这个和限制2的原因是一样的, 首先构造器要保证当前对象所有的属性都有值, 而Swift中的非Optionl类型的变量没有默认值, 所以只有当所有属性都有默认值的情况下Swift才能提供默认构造器.

4: 如果你自定义了构造器, 那么默认构造器会丢失(如果是结构体,还将丢失逐一成员构造器)

这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器. 如果你想保留默认构造器逐一成员构造器以及你自己定义的构造器,你可以将自定义构造器定义到extension

5. 指定构造器与便利构造器是针对类而言的, 值类型的构造器没有这一说

6. 指定构造器必须总是向上代理, 便利构造器必须总是横向代理

之所以要这样规定, 官方的说法是为了简化指定构造器与便利构造器之间的调用关系:
1: 指定构造器必须调用其直接父类的的指定构造器
2: 便利构造器必须调用同一类中定义的其它构造器
3: 便利构造器必须最终导致一个指定构造器被调用

7. 两段式构造过程

  • 第一个阶段: 每个存储属性都获得一个初始值
  • 第二个阶段: 进一步定制存储属性的值

两段式构造过程可以防止属性在初始化之前被访问, 也可以防止属性被另外一个构造器意外地赋予不同的值.
这个怎么理解呢? 若属性在初始化之前(第一阶段结束之前)被访问, 显然这种情况是没有意义的,因为这个时候属性还没有任何值, 另外一种情况若当前被访问的属性是从父类继承过来的, 如果它在初始化之前被访问,也是没有意义的, 因为它会被后面的super.init方法重新赋予初始值.

我对两段式构造过程的理解: 之所以要分两段式来构造和Swift引入Optional的概念还是分不开的, Swift中的变量默认都不是Optional的, 这些变量不像其它语言(如Java)中的那样会默认有初始值, 所以必须需要第一阶段来确定一个初始值.换个角度来看, 其它语言的构造器相当于直接是从第二个阶段开始的, 它们的第一阶段相当于是在定义属性时(这个时候系统会默认分配一个默认值, 如果你不显式的赋予它初始值). 所以其实,如果你在Swift中给你的属性在定义时就显式的赋予了初始值,或者你的属性是Optional的, 那么你在写构造器时也就没有这些限制了, 因为这个时候在init方法中你相当于是直接从第二个阶段开始的.

8. 4种安全检查

  1. 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器.
  2. 指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  3. 便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
  4. 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。

这4项中只有第一条比较难理解一些,当初困扰了好久,后来在万能的stackoverflow上找到的答案,详情

9. Swift中的子类默认不会继承父类的构造器

Swift的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。父类的构造器仅会在安全和适当的情况下被继承。

10. 你只能重写父类的指定构造器, 无法重写父类的便利构造器

为了简化构造器之间的调用关系, 便利构造器必须总是横向代理. 也就是说在便利构造器中是无法访问父类的构造器的, 也就不能完成从父类中继承下来的属性的初始化, 导致无法完成子类的完全初始化, 所以子类是不能重写父类的便利构造器的.

11. 子类不仅可以重写父类的指定构造器, 还可以将其重写为便利构造器

12. 构造器的自动继承

  • 若子类中引入的存储属性都有缺省值, 且子类中没有定义任何构造方法. 那么子类将会自动继承父类中所有的构造方法(包括便利构造方法)
  • 若子类只是重写了父类中的部分构造方法, 则子类不再会自动继承父类中的其他构造方法
  • 若子类重写了父类中的所有指定构造方法, 则子类同时会自动继承父类中所有的便利构造方法
    我的理解: 父类的构造器只会在安全和适当的情况下被继承, 满足上面三种条件中的任何一条, 子类去自动继承父类中的构造方法都是适当的,不会引起任何构造过程的混乱.

13. 可失败构造器的参数列表不能与其它非可失败构造器的参数列表相同

若参数列表相同, 则可以认为两个方法是同一个方法, 而一个方法不能同时是非可失败与可失败的

14. 可失败构造器,在返回nil前必须要将所有的stroed property赋值

从swift2.2开始已没有此限制


版权声明

文章版权归本人所有,如需转载需在明显位置处保留作者信息及原文链接 !