Swift学习笔记14——初始化(Initialization)和析构(Deinitialization)其一

所谓的初始化,就是当你创建一个类、结构体、枚举类的时候,完成存储属性的值的初始化,和其他一些初始化工作。准备好这个实例以供使用。

反过来,当一个实例不再使用的时候,由析构过程释放这个实例所占用资源。

完成初始化工作的一个方法一般称为初始化方法、构造器、构造方法等等。英文:Initializer。我就翻译为构造器吧。因为感觉定义的时候没有加func关键字,而且是单词是er结尾。

Swift中的构造器不需返回值,只是完成初始化工作。


类或结构体在创建的时候必须保证存储属性有初始化值。这个目的的实现可以通过在定义属性的时候给存储属性添加上默认值,也可以在构造器中赋值。并且在这两种情况中都不会触发属性的观察器。官方文档建议我们在一个存储属性通常具有同一个初始值的时候,使用属性定义的方式添加初始值。

下面是这两种方法的代码

class Animal {
    var life: Int = 0
    var height: Double
    init(){
        height = 0
    }
}

这里的init()方法就是构造器。注意它是不带func关键字的。而且也没有返回值。

       以前我们创建一个实例的时候,都是使用“类名()”的形式,这其实就是调用了这个构造器。但是当时我们并没有写这个构造器,这是因为当一个类没有其他构造器的时候,编译器会自动生成这个默认的构造器。但是一旦有了其他的构造器,那么系统就不会再默认生成这个构造器。你也就再不能用“类名()”这种创建方法了。当然,当有其他构造器的时候,你也可以手动添加这个默认的构造器。

自定义构造器

我们可以自己定义构造器,可以传入自定义的参数列表来初始化类。自定义的构造器的外部参数名规则和方法不太一样,构造器的每一个参数都会生成和局部参数名一样的外部参数名。如果不想要外部参数名,还是可以用下划线取消。

class Animal {
    var life: Int = 0
    var height: Double
    init(){    //注意,当有其他构造器的时候,系统不会再生成这个构造器,必须手动写出来
        height = 0
    }
    init(life: Int, height: Double) {
        self.life = life
        self.height = height
    }
}

//可以这样使用自定义的构造器
var a = Animal(life: 29, height: 1.2)

再次提醒,当有类定义里面有自定义构造器的时候,不会再自动生成默认构造器。并且对于结构体也再不会自动生成遍历属性的构造器。

但是你可以将你自己的构造器写在扩展里面,这样就不会影响默认构造器的自动生成。



可选类型属性和常量属性

可选类型属性是具有默认的nil值,所以你在初始化的时候是可以不为它赋初始值的。

常量属性必须在初始化完全前赋初始值,一旦赋值之后就不能再修改。在子类里面也不能修改。


构造器委托(Initializer Delegation)

构造器委托,就是在一个构造器里面调用另外一个构造器,这样可以减少多余的代码。在构造器里面使用self.init语法来调用另外一个构造器,且这个语法只能在构造器里面使用。

构造器委托在值类型(结构体和枚举类)和类直接是不太一样的。因为类需要考虑到继承问题。

先来看值类型的构造器委托。这个很简单,调用另外一个构造器就行了。下面的例子虽然有点脱裤子放屁的感觉,但。。就是举个例子而已。

struct Apple {
    var totalPrice: Double
    
    init(totalPrice: Double) {
        self.totalPrice = totalPrice
    }
    init(kg: Double, pricePerKg: Double) {
        let totalPrice = kg * pricePerKg
        self.init(totalPrice: totalPrice)   //调用另外一个构造器
    }
}


类的继承和初始化

因为类可以继承,所以它的初始化规则就变得比结构体要复杂多了。

首先,类的构造器分为指定构造器(Designated Initializer) 和  便利构造器 (Convenience Initializer)

一个类通常具有很少数量(一般只有1个)的指定构造器。但是必须最少具有一个。

便利构造器是用来调用指定构造器完成初始化工作的。一个类可以不具有遍历构造器。

两种构造器的语法

//指定构造器
init(parameters) {
    statements
}
//便利构造器
convenience init(parameters) {
    statements
}


类的构造器委托规则:

1、一个指定构造器必须调用它的直接父类的指定构造器

2、一个便利构造器必须调用同一个类的另外的构造器

3、一个便利构造器必须最终以调用指定构造器结束。

规则2和3连起来就是说,一个便利构造器可能会调用了另外一个便利构造器,然后这个构造器又调用了另外一个便利构造器....但是这个链的最后的一个便利构造器必须是调用指定构造器。
下面附上官方的图片。在图片上可以看到,三条规则总结起来就是:指定构造器往上调用,便利构造器平行调用且终结在指定构造器。

Swift学习笔记14——初始化(Initialization)和析构(Deinitialization)其一_第1张图片


类的初始化分为两阶段

第一阶段

1、一个指定构造器或便利构造器被调用

2、分配类实例所需的内存,但是内存没有初始化

3、这个类的指定构造器确保该类引入的新的存储属性都初始化。(因为便利构造器最终会到指定构造器)。

4、然后这个构造器调用父类的指定构造器完成和第三步同样的工作。

5、直到继承链的最顶部分。

6、这时候有的存储属性都有初始值了。

第二阶段

1、从继承链的最顶部分出发。每个指定构造器具有了修改实例的权利。并且可以调用实例方法。

2、最后,这个初始化链条里面的便利构造器都具有了修改实例的权利。


遵循上面两个阶段的步骤,避免了当在初始化链后面的构造器修改了属性之后,又被前面的构造器修改属性的可能性。避免了使用没有经过初始化的属性的可能性。

为了确保两个阶段步骤的实施。编译器有4条检测规则,这四条规则决定了你在构造器里面某些代码的顺序。

1、在调用父类构造器之前,必须确保子类引入的新属性已经得到初始化。

2、子类必须在调用了父类的构造器之后,才能访问继承的属性。

3、便利构造器必须在调用了其他构造器之后,才能访问属性或调用实例方法。

4、在完成第一阶段之前(其实就是完成初始化本类定义的新属性,调用父类构造器),构造器不能调用实例方法,不能访问属性的值,不能把self当做值访问。但是可以用self.属性名的方式给属性赋值。

下面可以看看分别违反每个规则的例子

class Animal {
    var life: Int
    var height: Double
    init(getLife: Int, getHeight: Double) {
        self.life = getLife
        self.height = getHeight
    }
}

class Dog: Animal {
    var name: String
    init(){
        super.init(getLife: 2, getHeight: 2.0)  //这句报错,违反第一条,必须先将本类引入的属性初始化才能调用父类构造器
        self.name = "Larry"                     //所以这一句必须写在前面才没错
    }
    init(name: String, getLife: Int, getHeight: Double){
        self.name = name
        self.life = 1                            //这句报错,违反第二条,必须调用了父类构造器之后才能访问继承的属性。
        super.init(getLife: getLife, getHeight: getHeight)    //这句必须放在第二句之前才没错
    }
    convenience init(getLife: Int) {
        self.name = "Larry"         //这句报错,违反第三条,便利构造器里面必须调用了其他构造器之后才能访问属性
        self.init()                 //这句应该放在前面
    }
    init(getName: String) {
        self.name = getName
        Dog.sayHello()
        self.printName()          //这句报错,违反第四条,必须完成了阶段1才能调用实例方法或访问属性
        super.init(getLife: 21, getHeight: 2.3)   //这句应该放在前面
    }
    
    func printName(){
        print(self.name)
    }
    static func sayHello(){
        print("hello")
    }
}

这里补充一下属性观察器,以前说过,属性观察器在构造器里面给属性赋值的时候是不会被调用的。但是这个只针对于本类定义的属性。对于继承而来的属性,当已经经过父类的构造器完成初始化后,如果在子类里面修改了继承属性的值,那么就会触发父类观察器,如果你在子类里面也覆盖了这个观察器,那么子类和父类的观察器都会被调用。


另外再看

class Animal {
    var life: Int = 0 {
        willSet{
            print("animial life newValue \(newValue)")
        }
    }
    init(getlife: Int){
        print("animal life init \(self.life)")
        self.life = 1
        print("animal life after assign \(self.life)")
    }
}

如果我们调用下面的语句,

var a = Animal(getlife: 3)
//打印
//animal life init 0
//animal life after assign 1
注意一点,我们在init里面第一句已经用到了self。根据编译器规则第四条,不允许在属性没有初始化的前进行访问。从上面代码通过编译来看,这里编译器已经将属性初始化为定义时候的值了。如果你将定义时候的默认值删去,那么第一句就会报错。


Swift默认是不继承父类的构造器的。
如果你想要和父类一样的构造器,那么必须自己写。如果你写的构造器(不管是指定还是便利)和父类的指定构造器一样,那么这个构造器前必须加上override关键字。如果你写的构造器和父类的便利构造器一样,那么不用加override,因为子类永远不能直接调用父类的便利构造器。

但是在某些情况下,子类可以自动继承父类构造器。

前提条件:子类新引入的属性都必须具有默认值

规则1:如果子类没有定义任何的指定构造器。那么子类自动继承父类所有的指定构造器。

规则2:如果子类实现了父类所有的指定构造器(可以通过规则1继承而来,也可以通过重写而来),那么子类自动继承父类所有的便利构造器。

例子

class Animal {
    var life: Int
    var height: Double
    convenience init(getLife: Int){
        self.init(getLife: getLife, getHeight: 0.0)
    }
    init(getLife: Int, getHeight: Double) {
        self.life = getLife
        self.height = getHeight
    }
}

class Dog: Animal {
    var name: String = "Larry"
    //    override init(getLife: Int, getHeight: Double){
    //        super.init(getLife: getLife, getHeight:getHeight)
    //    }
    
}


无论上面Dog类里面注释的代码是否打开,Dog都继承了Animal的所有构造器。


本来想一次写完了,但是太多了,分两篇。

你可能感兴趣的:(Swift学习笔记)