Cowboy Tech

Swift中类的构造与析构

类和结构体的构造器

  1. Swift的构造器构造出来的实例由系统隐式返回,不用return
  2. 构造器的作用完成每个类,结构体中实例存储属性的初始化
  3. 为实例存储属性赋初始值的时机:1.定义实例存储属性时指定初始值。2.在构造器中为实例存储属性指定初始值.
  4. swift只为类提供一个无参数的构造器
  5. swift为结构体提供两个构造器:无参数的构造器和初始化所有实例存储属性的构造器

构造器代理:

在定义构造器时,通过self.init(实参)调用其他构造器来完成实例的部分构造过程,就是A构造器调用B构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct Apple
{
var name: String
var color: String
var weight: Double!
init(_ name: String , _ color:String)
{
self.name = name
self.color = color
}
// 两个参数的构造器
init(name: String , color:String)
{
self.init(name , color) // 构造器代理
}
// 为构造器显式指定外部形参名
init(appleName n: String , appleColor c:String)
{
// name = "临时值" // 这行代码将导致错误
self.init(n , c) // 构造器代理
// 调用构造器代理之后,即可通过self访问该实例的属性
print("--执行显式指定外部形参名的构造器--\(self.name)")
}
// 定义三个参数的构造器
init(name: String , color: String , weight: Double)
{
self.init(name , color) // 构造器代理
self.weight = weight
}
}
var ap1 = Apple("红富士" , "粉红色")
print("\(ap1.name)--->\(ap1.color)")
var ap2 = Apple(appleName:"青苹果" , appleColor:"绿色")
print("\(ap2.name)--->\(ap2.color)")
var ap3 = Apple(name:"美国苹果" , color:"红色" , weight:0.45)

指定构造器

一个类中至少有一个指定构造器,其必须负责初始化类中所有的实例存储属性。

指定构造器

便利构造器

  1. 便利构造器属于次要的,辅助性的构造器
  2. 类中可以不定义便利构造器,便利构造器必须调用同一个类中的其他构造器完成初始化
  3. 只有类中才有便利构造器的概念

安全检查3

1
2
3
4
便利构造器的语法格式:
convenience init(形参){
//
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Apple{
var name:String!
var weight:Double
//定义指定构造器
init(name:String, weight:Double){
self.name = name
self.weight = weight
}
//定义便利构造器,使用convenience修饰
convenience init(n name:String, w weight:Double){
//便利构造器必须调用同一个类中其他构造器
self.init(name: name, weight: weight)
self.name = name
}
}
var app1 = Apple(n: "Red apple", w: 1.2)
var app2 = Apple(name: "Alvocado", weight: 2.4)

类的构造器链

规则

  1. 子类构造器必须调用直接父类的指定构造器(如果有父类)
  2. 便利构造器必须调用同一个类中的其他构造器;
  3. 便利构造器调用的构造器链的最终节点必须是指定构造器

简化记忆为:

  1. 指定构造器总是向上代理(调用父类构造器)
  2. 便利构造器总是必须横向代理(调用当前类的其他构造器)

类的构造器链1

类的构造器链1

类的构造器链2

类的构造器链2

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Fruit {
var name:String
var weight:Double
init (name:String) { //1
self.name = name
self.weight = 0.0
}
//定义便利构造器
convenience init (name:String,weight:Double){//2
//调用本类中的1号指定构造器
self.init(name:name)
self.weight = weight
}
//定义另一个便利构造器
convenience init(n name:String,w weight:Double){//3
//调用本类中2号便利构造器
self.init(name:name,weight:weight)
}
}
class Apple:Fruit {
var color:String
init(name:String,weight:Double,color:String){//4
self.color = color
//子类指定构造器必须调用父类的指定构造器,此处调用父类1号构造器
super.init(name: name)
self.weight = weight //为什么不能放在init前面?因为父类构造器会把值给覆盖
}
init(){//5
self.color = ""
//子类指定构造器必须调用父类的指定构造器,此处调用父类1号构造器
super.init(name: "")
self.weight = 0.0
}
//定义一个便利构造器
convenience init(name:String,color:String) {//6
//调用本类中4号指定构造器
self.init(name:name,weight:0.0,color:color)
}
//定义一个便利构造器
convenience init(n name:String, c color:String){//7
//调用本类中6号便利构造器
self.init(name:name,color:color)
}
}

两段式构造

第一阶段

  1. 程序调用子类的某个构造器
  2. 为实例分配内存,此时实例的内存还没有被初始化
  3. 指定构造器确保子类定义的所有实例存储属性都已被赋初值
  4. 指定构造器将调用父类的构造器,完成父类定义的实例存储属性的初始化
  5. 沿着调用父类构造器的构造器链一直往上执行,直到到达构造器链的最顶部

第一阶段

第二阶段

  1. 沿着继承树往下,构造器此时可以修改实例属性,访问self,甚至可以调用实例方法
  2. 最后,构造器链中的便利构造器都有机会定制实例和使用self

第二阶段

安全检查规则

  1. 指定构造器必须先初始化当前类中定义的实例存储属性,然后才能向上调用父类构造器
  2. 指定构造器必须先向上调用父类构造器,然后才能对继承得到的属性赋值
  3. 便利构造器必须先调用同一个类的其他构造器,然后才能对属性赋值。否则如果先赋值,在调用,那就会把刚刚赋值的给覆盖掉
  4. 构造器在第一阶段完成之前,不能调用实例方法,不能读取实例属性
  5. 总之,建议实例存储属性指定初始值

安全检查代码示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Fruit {
var name:String
init(name:String){
self.name = name
}
}
class Apple:Fruit {
var color:String
init(color:String){
//print(self.color) // error
self.color = color
//print(self.color)
self.color = "Red"
//print(super.name)// error
super.init(name: "Flower apple")
//print(super.name)
super.name = "red apple"
}
}
var a = Apple(color: "Green")

安全检查代码示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Fruit {
var name:String
init(name:String){
self.name = name
}
}
class Apple:Fruit {
var color:String
init(color:String){
self.color = color
print(self.color)
super.init(name:"Red apple")
}
//定义便利构造器
convenience init (name:String,color:String){
//调用其他构造器之前,不能访问或修改任何实例存储属性,下面两行代码出错
//print(self.color)
// super.name = name
//调用本类其他构造器
self.init(color:"Red")
}
}
var a = Apple(color: "Red")

继承

Swift的子类不会自动继承父类的构造器,若继承,则满足如下规则:

1.如果子类没有提供任何指定构造器,那么它将自动继承父类的所有指定构造器
2.如果子类实现了父类所有的指定构造器,无论如何实现的,都将自动继承父类的所有便利构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Fruit {
var name:String
var weight:Double
//定义一个指定构造器
init(name:String,weight:Double){ //1
self.name = name
self.weight = weight
}
//定义2个便利构造器
convenience init(name:String){ //2
self.init(name:name,weight:0.0)
}
convenience init(){ //3
self.init(name:"Fruit")
self.weight = 1.0
}
}
//该类提供了指定构造器,并未实现父类所有的指定构造器,所以根据规则,就如下两个构造器,不继承父类的
class Apple:Fruit{
var color:String
//定义一个指定构造器
init(name:String,color:String,weight:Double){ //4
self.color = color
super.init(name: name, weight: weight)
}
//定义一个便利构造器
convenience init(name:String,color:String){ //5
self.init(name:name,color:color,weight:0.0)
}
}
//该类没有定义任何构造器,所以根据规则继承父类的所有
class Fu:Apple{
var vitamin:Double = 0.21
}

重写

  1. 子类构造器重写了父类的指定构造器,必须添加override修饰符
  2. 子类中定义的构造器只是与父类中便利构造器的形参列表,外部形参名相同,不算重写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class Fruit {
    var name:String
    var weight:Double
    init(){ //1
    self.name = ""
    self.weight = 0.0
    }
    init(name:String,weight:Double){ //2
    self.name = name
    self.weight = weight
    }
    convenience init(name:String){ //3
    self.init(name:name,weight:0.0)
    }
    convenience init(_ name:String){ //4
    self.init(name:name)
    }
    }
    class Apple:Fruit {
    var color:String
    //重写父类的指定构造器 //override 2
    override init(name:String,weight:Double){
    self.color = "Default"
    //调用父类构造器
    super.init(name:name, weight:weight)
    }
    //定义指定构造器 //5 这个和父类的便利构造器名和形参名都相同,但是不算做重写
    init(name:String){
    self.color = "Default"
    super.init(name: name, weight: 0.0)
    }
    //便利构造器
    convenience init(name: String, weight: Double,color:String) { //6
    self.init(name:name,weight:weight)
    self.weight = weight
    }
    //使用便利构造器重写父类的指定构造器 //override 1
    override convenience init() {
    self.init(name:"Apple",weight:0.0,color:"Pink")
    }
    }

可能失败的构造器

  1. 可能失败的构造器:使用Init?或者!关键字进行定义
  2. 在构造器执行体中使用return nil 来表示构造器失败
  3. Swift不允许定义两个相同形参列表的构造器。即使一个是可能失败的构造器,一个是普通的构造器也不行

可能失败的构造器必须满足如下两个条件才能触发:

  1. 该类中的所有实例存储属性都已被赋初始值
  2. 所有的构造器调用都已经被执行
  3. 可能失败的构造器可以调用同一个类中的普通构造器
  4. 普通构造器不能调用同一个类中可能失败的构造器init?,但可以调用init!隐式解析的构造器
  5. 结构体中,普通构造器却可以调用同一个结构体中可能失败的构造器

    代码1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    struct Cat
    {
    let name: String
    init?(name: String) {
    // 如果传入的name参数为空字符串,构造器失败,返回nil
    if name.isEmpty {
    return nil
    }
    self.name = name
    }
    }
    let c1 = Cat(name: "Kitty")
    if c1 != nil {
    // 创建c1的构造器是init?,因此程序必须对c1执行强制解析
    print("c1的name为:\(c1!.name)")
    }
    let c2 = Cat(name: "")
    print(c2 == nil) // 输出true,表明c2为nil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum Season{
case Spring, Summer, Autumn, Winter
// 使用init!定义可能失败的构造器,则该构造器创建的实例可进行隐式解析
init!(name: Character) {
// 根据传入的构造器参数选择相应的枚举成员
switch name {
case "S","s":
self = .Spring
case "U", "u":
self = .Summer
case "A", "a":
self = .Autumn
case "W", "w":
self = .Winter
// 如果传入其他参数,构造失败,返回nil。
default:
return nil
}
}
}
let s1 = Season(name: "s")
if s1 != nil {
print("Season实例构造成功!")
}
let s2 = Season(name: "x")
print(s2 == nil)

代码2

1
2
3
4
5
6
7
8
9
10
class User {
var name :String //如果没有下面的赋值语句。根据规则1. 如果这个是可选类型 var name: String! ,那这样也可以,算是已经赋值nil了。
init?(name:String){
//self.name = "" // 根据规则1.必须先赋值,才能return nil
if name.isEmpty{
return nil //根据规则2. return nil 必须在super init 和 self init语句之后
}
self.name = name
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class User{
var name: String
init?(name:String){ //如果改为init!, 那就是隐式解析,子类中的普通构造器也可以调用它
self.name = ""
if name.isEmpty{
return nil
}
self.name = name
}
}
class Student:User {
var grade:Int!
init!(name:String,grade:Int){
//调用父类的可能失败的构造器
super.init(name: name)
print("--super.init(name:\(name))之后--")
//如果grade小于1,使用return nil触发构造失败
if grade < 1{
return nil
}
self.grade = grade
}
}
let s1 = Student(name: "Tom", grade: 4)
print("s1的name:\(s1.name),s1的grade:\(s1.grade)")
let s2 = Student(name: "Jerry", grade: 0)
print(s2 == nil)
let s3 = Student(name: "", grade: 3)
print(s3 == nil)

重写可能失败的构造器

  1. 子类可以用可能失败的构造器或者普通的构造器重写父类中的可能失败的构造器
  2. 子类的普通构造器不能向上调用父类的可能失败的构造器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class Bird {
    var name:String!
    //定义普通构造器
    init(){}
    //定义可能失败的构造器
    init?(name:String){
    if name.isEmpty{
    return nil
    }
    self.name = name
    }
    }
    class Sparrow:Bird {
    let weight:Double!
    init?(name:String,weight:Double){
    //由于该构造器是可能失败的构造器,可以调用父类可能失败的构造器
    super.init(name: name)
    if weight <= 0 {
    return nil
    }
    self.weight = weight
    self.name = name
    }
    //使用普通构造器重写父类的构造器
    override init(name: String) {
    //由于该构造器是普通构造器,因此不能调用父类的可能失败的构造器
    //因此只能调用父类的普通构造器
    super.init()
    if name.isEmpty {
    super.name = "麻雀"
    }
    self.name = name
    }
    }
    let sp1 = Sparrow(name: "")
    print("sp1的name:\(sp1.name)")
    let sp2 = Sparrow(name: "", weight: 2.2)
    print(sp2 == nil)
    let sp3 = Sparrow(name: "小黄嘴", weight: 0)
    print(sp3 == nil)

required构造器

  1. Swift允许在父类构造器前添加require关键字,用于声明所有子类必须包含该require构造器
  2. 父类中声明的required构造器即可是指定构造器,也可以便利构造器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class Fruit {
    var name:String
    var weight:Double
    required init(name:String){//1
    self.name = name
    self.weight = 0.0
    }
    required convenience init(name:String,weight:Double){//2
    self.init(name:name)
    self.weight = weight
    }
    }
    class Apple:Fruit {
    var color:String
    required init(name:String,weight:Double){// override 2
    self.color = "Pink"
    super.init(name: name)
    }
    init(color:String){
    self.color = color
    super.init(name: "")
    }
    required convenience init(name:String){//override 1
    self.init(color:"aaaa")
    super.name = name
    }
    }
    //该类将会继承得到父类的request构造器
    class Grape:Fruit{
    var sugarRate:Double = 0.45
    }

析构器

  1. 析构器是一个名为deinit的函数,不需要使用func关键字,无参数和返回值
  2. 析构器在实例释放之前由系统自动调用,不要主动调用析构器
  3. 子类自动继承父类的析构器,而且无论如何,子类析构器一定会调用父类析构器
  4. 析构器可以访问该实例的所有实例存储属性,或者根据这些属性来关闭资源,比如关闭哪个文件,哪个程序链接
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Fruit {
    var name:String
    var weight:Double
    //定义指定构造器
    init(name:String){
    self.name = name
    self.weight = 0.0
    }
    //定义析构器
    deinit{
    print("程序准备释放fruit")
    //此处可访问实例属性,可用于释放资源
    }
    }
    class Apple:Fruit {
    var color:String
    //定义指定构造器
    init(name:String,weight:Double,color:String){
    self.color = color
    super.init(name: name)
    }
    //定义析构器
    deinit{
    print("程序准备释放apple")
    //此处可访问实例属性,可用于释放资源
    }
    }
    var ap:Apple? = Apple(name: "红富士", weight: 0.34, color: "红色")
    print(ap!.name + "-->" + ap!.color)
    ap = nil //此处会自动调用析构器

总结

  1. 便利构造器不是必要的,使用convenience关键字,横向调用类中的构造器,代码执行顺序:先调用构造器,再赋值
  2. 指定构造器,一个类中至少有一个指定构造器,其必须负责初始化类中所有的实例存储属性
  3. 指定构造器的代码执行顺序: 初始化属性–>赋值–>super init—> 赋值继承得到的属性
  4. 类的构造第一阶段, 初始化属性–>赋值–>super init—> 赋值继承得到的属性 —>调用该属性
  5. 类的构造第二阶段, 赋值或调用
  6. 总之建议变量初始化的时候就直接赋值,最好。
  7. 构造器的继承,子类有自己的就用自己的,没有就继承父类的全部
  8. 重写使用override
  9. 可能失败的构造器
  10. 重写可能失败的构造器
  11. required构造器
  12. 析构器