Swift学习之构造与析构

版权声明:不积跬步无以至千里,不积小流无以成江海! https://blog.csdn.net/Bolted_snail/article/details/87917973

简介

  • 结构体和类在创建实例的过程中需要进行一些初始化工作,这个过程称为构成过程;相反这些实例最后被释放的时候需要进行一些清楚资源的工作,这个过程称为析构过程.
  • Swift中结构体和类的实例在构造过程中会调用一种特殊的方法init方法,称为构造函数;在析构过程中会调用deinit方法,称为析构函数。(结构体或者类创建实例直接结构体名或类名后面加个小括号,其实就是调用了默认的构造方法init())。

构造函数

  • 构造函数的主要作用是初始化实例,其中包括:初始化存储属性和其他初始化。如果在构造函数时初始化了这些存储属性,那么在定义这些属性时就不用初始化了。计算属性不保存数据,所以不需要初始化,构造函数也不能初始化静态属性,因为他们与具体实例个体无关。枚举没有存储属性,所以也没有init的构造函数。
init() {
    }

示例:

class Employee{
    let no: Int
    var name: String
    init() {
        no = 0
        name = ""
    }
    
}
  • 结构体中默认的构造函数:
struct Rectangle{
    var width:Double = 0.0
    var height = 0.0
}
Rectangle(width: 10.0, height: 10.0)

上面结构体重使用了默认构造函数,结构体中按照从上到下的顺序吧属性名作为参数标签,依次提供参数,不用声明构造函数,直接可以按照属性参数标签赋值初始化实例。注意这种默认构造函数,值使用于结构体,在类中不能使用。与oc不同的是Swift的构造函数没有返回值。

构造函数重载
  • Swift中构造函数可以有多个,但他们的参数列表不同,这些构造函数构成重载。
class Rectangle {
    var width: Double
    var height: Double
    init() {
        width = 0.0
        height = 0.0
    }
    init(W witdh:Double, H height: Double) {
        self.width = witdh
        self.height = height
    }
    init(length: Double) {
        self.width = length
        self.height = length
    }
}
let r1 = Rectangle()
let r2 = Rectangle(W: 100, H: 100)
let r3 = Rectangle(length: 100)

当属性名称和构造函数参数名相同时,添加self.来区分是属性函数参数。

构造函数代理
  • 为了减少多个构造函数间的代码重复,在定义构造函数时可以通过调用其他构造函数来完成部分构造过程,这个过程称为构造函数代理。但是结构体和类构造函数代理是有所不同的。
结构体构造函数代理
struct Rectangle {
    var width: Double
    var height: Double
    init() {
        self .init(W: 100, H: 100)
    }
    init(W witdh:Double, H height: Double) {
        self.width = witdh
        self.height = height
    }
    init(length: Double) {
        self.init(W: length, H: length)
    }
}
let r1 = Rectangle()
let r2 = Rectangle(W: 100, H: 100)
let r3 = Rectangle(length: 100)

结构体因为不存在继承,所以构造函数代理很简单,就是在同一类型中通过self.init(...)语句来调用其他构造函数,self.init构造函数就称为构造函数代理

类造函数代理
  • 由于类存在继承关系,类构造函数代理比较复杂,分为横向代理向上代理
  • 横向代理类似于结构体的构造函数代理,发生在同一类内部,这种构造函数称为便利构造函数(convenience initializer)
class Rectangle {
    var width: Double
    var height: Double
    convenience init() {
        self .init(W: 100, H: 100)
    }
    init(W witdh:Double, H height: Double) {
        self.width = witdh
        self.height = height
    }
    convenience init(length: Double) {
        self.init(W: length, H: length)
    }
}
let r1 = Rectangle()
let r2 = Rectangle(W: 100, H: 100)
let r3 = Rectangle(length: 100)

类中构造函数横向代理必须前面加上convenience关键字

  • 向上代理发生在继承情况下,在子类构造过程中要先调父类构造函数,初始化父类的存储属性,这种构造函数称为指定构造函数(designated initializer)
class Person{
    var name: String
    var age: Int
    func description() -> String {
        return "\(name)的年龄是\(age)"
    }
    init(name:String, age:Int) { //指定构造函数,没调用self.init
        self.name = name
        self.age = age
    }
    
    convenience init(name:String) { //便利构造函数,调用了self.init
        self.init(name: name, age: 18)
    }
    
    convenience init(){ //便利构造函数,调用了self.init
        self.init(name: "Jack MA")
        self.age = 19 //未初始化的属性要放在self.init之后,放在前面会编译报错
    }
}
class Student: Person {
    var school: String
    init(name: String, age: Int,school:String) {//指定构造函数,虽然调用了super.init,但没调用self.init
        self.school = school  //因为没调self.init所以可以school赋值,默认会在构造过程中初始化,如果放在super.init后面初始化就会报错
//        self.test() //编译报错
        super.init(name: name, age: age)
//        super.init(name: "马云")
        self.name = ""
        self.age = 18//age是属性父类的属性,所以要在super.init赋值才有意义,同样放在前面会编译报错
        self.test()
    }
    override convenience init(name: String, age: Int) { //虽然是重写父类的构造函数,但是还是调用了self.init,所以还是便利构造函数
        self.init(name: name, age: age, school: "杭州师范大学")
        self.school = "清华大学" //放在self.init之前会报错,因为只有在调用了指定构造函数后才会为存储属性分配内存和初始化
        self.name = ""
    }
    
    func test() {
        print("这是一个实例方法")
    }
}

Swift限制类的构造函数之间代理调用的规则

  • 指定构造函数必须调用其直接父类的指定构造函数。(也就是说如果一个类有父类,那么他的指定构造函数必须调用父类的指定构造函数,调用父类的便利构造函数或者不调用super.init编译都会报错。)
  • 便利构造函数必须调用同一类中定义的其他构造函数。
  • 便利构造函数必须最终以调用一个指定构造函数结束。(也就是说一个类中如果有便利构造函数的话,最少有一个调用了该类的指定构造函数)

构造过程安全检查

  • Swift 中类的构造过程分为两个阶段:
    1. 第一阶段,首先分配内存,初始化子类存储属性,沿构造函数链向上初始化父类存储属性,到达构造函数链顶部(调用了基类构造函数),初始化全部的父类存储属性。(Student类自己的school属性要优先初始化,然后调用super.init去初始化父类的属性name和age)。
    2. 第二阶段,从顶部构造函数链往下,可以对每个类进行进一步的修改存储属性调用实例方法等处理。(Student类的构造函数要想去修改父类的name后age属性或者去调用父类的description方法,必须在调用了super.init之后才能去修改和调用,否则编译报错)
  • 为确保构造过程顺利完成,Swift提供了4中安全检查:
    1. 指定构造函数必须保证其所在类的所有存储属性都完成初始化,之后才能向上调用父类构造函数代理。(Student类自己的school属性要优先初始化,然后调用super.init)
    2. 指定构造函数必须向上调用父类构造函数代理,然后再为基础属性设置新值。(Student类的构造函数要想去修改父类的name后age属性,必须先调用了super.init)
    3. 便利构造函数必须先调用同一类中的其他构造函数代理,然后再为任意属性赋新值。(Student的便利构造函数必须先调用self.init构造函数代理,才能去修改器school,name,age等存储属性)
    4. 构造函数在第一阶段构造完成之前不能调用实例方法,也不能读取实例属性,因为这时还不能保证要访问的实例属性已被初始化。(Student的实例方法test必须在super.init之后才能被被调用)
  • 两段式构造过程中,第一阶段构造完成的标志是:调用完父类的指定构造函数,即super.init语句;如果没有调用父类构造函数,则是调用完本身的指定构造函数,即self.init。

构造函数继承

  • Swift中的子类构造函数有两种来源:自己编写和从父类继承。但并不是父类所有的构造函数都能继承下来,是有条件的,条件如下:
    1. 如果子类没有定义任何指定构造函数,它将自动继承父类的所有指定构造函数。(也就是说如果父类有多个指定构造函数,子类的指定构造函数只调用了一个父类的指定构造函数super.init或者没有重写父类所有的指定构造函数,那么子类不能继承父类的构造函数,包括自定构造函数和便利构造函数,初始化时只能用自己的构造函数)
    2. 如果子类提供了所有父类指定构造函数的实现,无论是通过条件1继承过来的,还是通过自己编写实现的,它都将自动继承父类的所有便利构造函数。(也就是如果子类不写任何构造函数,则会继承父类所有的构造函数,包括指定构造函数和便利构造函数,初始化时可以用自己的构造函数,也可以用父类的构造函数(包括父类继承来的构造函数)。

析构函数

  • 析构过程是在实例释放时清理一些资源,Swift析构过程中会调用析构函数deinit,没有返回值也没有参数,也不需要参数的小括号,所以不能重载。
class Rectangle {
    var width: Double
    var height: Double
    convenience init() {
        self .init(W: 100, H: 100)
    }
    init(W witdh:Double, H height: Double) {
        self.width = witdh
        self.height = height
    }
    convenience init(length: Double) {
        self.init(W: length, H: length)
    }
    
    deinit {
        print("调用析构函数,正在释放实例对象!!!")
        width = 0.0
        height = 0.0
    }
}
print("初始化")
var r : Rectangle? = Rectangle(length: 100)
print("将要设置为nil")
r = nil
print("释放")

析构函数

  • 析构函数只适用于类,不适用于枚举和结构体。Swift中内存管理采用自动引用计数(ARC),不需要析构函数释放不需要的实例内存资源,但还是有一些清除工作要在这里完成,如关闭文件,移除通知监听者等。

猜你喜欢

转载自blog.csdn.net/Bolted_snail/article/details/87917973
今日推荐