Swift 构造器探究

Swift 构造器探究

什么时候要用构造器?

对于类(Class)

其实在其他语言中,比如说Java对于属性的初始化没有严格的要求。甚至在Model层只有对应属性的get,set访问器。而在Swift中无论是对于结构体(Structure)还是类(Class),如果其中存在存储属性(stored property),那么必须在合适的地方给它赋初始值,也就是初始化。不能让它们成为不确定的状态,即没有初始化。关于初始化,Swift提供了两种方式。一种是属性定义的时候初始化,也就是赋默认值。 第二种是在构造器中初始化。这里做个小结:存储属性必须初始化,初始化的方式且只能在以上两种方式选择,并且至少包含其中一种。

而对于第一种,在Swift中又有两种初始化方式。第一种,是给予明确的值。比如说var name = "Bob"Swift的数据类型推断机制会自动推断出name是一个String类型变量,初始值为Bob。第二种,是Optional类型初始化,通常用在这个属性在程序运行过程中可能存在值也可能不存在值的时候。比如说var avatar: UIImage?一个人可能没有头像。假设avatarPerson类的一个存储属性,程序在运行过程,如果Person类创建一个实例let myPerson = Person(), 如果没有给myPerson.avatar赋值,那么myPersonavatar属性自动初始化为nil。如果myPerson.avatar = UIImage(named: "prettyGirl"),那么avatar属性初始化就是UIImage(named: "prettyGirl")

而对于第二种初始化方式,更加具体的说,是如果没有是实现第一种初始化方式的时候,必须实现的。也就是说如果你定义一个属性var avatar: UIImage既没有给予明确的初始值,也没有让它成为Optional类型,那么必须实现在构造器中的初始化。这就是什么时候要用构造器的重点了。
然而Swift的构造器又有两种,一种是designated构造器,一种是convenience构造器。所有designated构造器都必须初始化那些没有满足第一种情况的存储属性。注意这里是类中所有的designated构造器都必须要做这件事。具体怎么做请看后文Designated构造器

对于结构体(Structure)

Swift中结构体和类的构造器其实差不多。除了结构体中没有析构器(Deinitializer),不能够继承(inherit)以及结构体有memberwise构造器外大体上是一致的。所以你有时候看到一个结构体struct Point有两个存储属性var x: Double, var y: Double ,却没有任何构造器,但是他们既不符合类中讨论的第一种初始化方法(即赋默认值)。那么它们违背了语法规则吗?其实不是的,如果结构体没有自定义的构造器,Swift隐式创建了一个init(x:y:)的构造器其内容就相当于self.x = x, self.y = y。如果你手动给结构体加个空内容的构造器init(){ },编译器就报Return from initializer without initializing all stored properties的错误。也就是说,如果你创建了你自己定义的构造器,Swift就默默地帮你把memberwise构造器去掉了,而你自己定义的构造器又没有对存储属性初始化,那么这违背了语法规则。但是如果你想同时拥有这两个构造器(memberwise构造器和自定义构造器),你可以把自定义的构造写到Extension Point{ // custom initializer }里面进去。

构造器的继承

designated构造器

designated构造器在Swift中很常见,顾名思义这个构造器就是你类中所有构造器的“原型”。在这个构造器中只调用父类的designated构造器或者不调用其他任何构造器称为designated构造器。每个类都必须至少有一个designated构造器,但是你会看到有些情况看不见类中声明designated构造器,那是因为它是一个子类,如果不写任何designated构造器,将会自动继承父类所有designated构造器。我们将在下面的自动构造器继承中详细讲到。

    init(parameters){
        // statments
    }

convenience构造器

convenience构造器是第二种构造器。它主要是横向代理,就是说在convenience构造器中一定存在也只能存在该类的一个构造器通常用self.init(parameters)调用该类的一个构造器。当然convenience构造器不是必要的。

    convenience init(parameters){
        // 调用该类中的一个构造器
        self.init(parameters)

        // customize properties
    }

类的构造器代理规则

  • 规则1:子类中的designated构造器必须调用最近一级父类的desigated构造器
  • 规则2:convenience构造器必须在同一个类中调用其他一个构造器
  • 规则3:convenience构造器通过调用链(代理链)调用一个designated构造器

总结下也就说

  • desingated构造器必须一直向上代理(即调用最近一级父类的designated构造器)
  • convenience构造器必须横向代理,且代理终点为一个desingated构造器

下面这幅图(引用自苹果官方文档原图)就表明了这两点
技术分享
SubClass的convenience构造器调用了第二个designated构造器(符合规则2),第二个designated的调用了SuperClass的designated构造器(符合规则1),这表明了convenience构造器最终调用的是designatedg构造器(符合规则3)。同理其他的构造器调用亦是如此

下面再来一幅图(引用自苹果官方文档原图)
技术分享

Two-Phase初始化

第一阶段类中每个存储属性必须有初始值,一旦每个储值属性的初始状态被确定了,第二阶段就开始了。第二阶段就是在新的实例可用之前对初始值的修改阶段。利用两阶段初始化可以让初始化安全,防止属性值在初始化完成之前被访问,以及属性值被另外的构造器设置为不同的值。这和OC差不多,唯一区别就是OC在第一阶段初始的默认值只能是0或者是nil

初始化有安全检查机制

  • 安全检查1:一个designated构造器在向上代理之前必须初始化该类中定义的所有存储属性
  • 安全检查2:一个designated构造器必须先向上代理调用一个父类的构造器,在修改父类的属性值之前。如果不这样,那么你修改的属性值,会被父类的构造器初始化属性的时候覆盖掉。
  • 安全检查3:一个convenience构造器在对本类任何属性操作之前(包括父类的属性以及本类定义的属性)必须调用另外一个本类中的构造器。如果不是这样,那么修改完的属性很可能就被本类中的构造器初始化属性的时候覆盖了。
  • 安全检查4:一个构造器不能调用实例方法,读取任何实例属性的值,或者用作为一个值指向self知道第一阶段初始化结束

初始化两个阶段

第一阶段:

  • 一个designated或者convenience构造器在类中被调用
  • 类的新实例向系统申请内存空间,但是内存还没有被初始化
  • 该类的一个designated构造器确认所有在该类中定义的存储属性已经被初始化。这些属性的内存被初始化。
  • designated的构造器告知父类构造器对父类自己的属性执行相同的操作
  • 一直代理到类继承链的最高级
  • 一旦到达类继承链的最高级,并且链上的最终类确认了它所有的存储属性已经初始化完毕,则实例内存被完全初始化,然后第一阶段到此就完成了。

下面这幅图是第一阶段
技术分享

第二阶段:

  • 从继承链的最顶端开始每个designated的构造器可以修改实例中的属性值,此时构造器也可以访问self指针了,可以调用实例方法等等。
  • 最后在继承链上的任何convenience构造器可以用self指针来修改实例

下面这幅图是第二阶段
技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。