二.类的构造和析构(续)
1. Swift中,子类不会自动继承父类的构造器,只有满足如下规则时,子类才会自动继承父类的构造器:
(1)规则1:如果子类没有提供任何指定构造器,那么它将自动继承父类的所有指定构造器
(2)规则2:如果子类实现了父类所有的指定构造器,无论是通过规则1继承实现的,还是通过程序编码实现的,它都将自动继承父类的所有便利构造器。
2. 如果子类中定义的构造器与父类中指定构造器的形参列表、外部形参名相同,即可认为子类构造器重写了父类构造器,此时需要在构造器前面用override关键字修饰----override关键字强制Swift编译器执行严格的检查,以保证该构造器确实重写了父类构造器。
3. 只要子类构造器重写了父类的指定构造器,则必须在子类构造器前添加override修饰符,即使子类构造器是一个便利构造器(此时便利构造器前同时出现override convenience两个修饰符)
4. 对于父类的便利构造器来说:如果子类中定义的构造器只是与父类中便利构造器的形参列表、外部形参名相同----虽然看起来很像重写父类构造器,但由于子类永远不可以直接调用父类的便利构造器,因此这其实不算构造器的重写,所以也不用override关键字。
5. 类与可能失败的构造器:对于值类型(枚举、结构体)的可能失败的结构体而言,在该构造器实现体的任意地方都可以通过return nil来触发构造失败。对于类来说,可能失败的构造器必须满足如下两个条件之后才能触发:
(1)该类中的所有实例存储属性都已经悲哀赋初始值(既可由程序显式地指定初始值,也可由系统隐式分配默认的初始值)
(2)所有的构造器调用都已经被执行(即:return nil不能位于self.init(参数)或super.init(参数)代码之前)
6. 举个栗子:
class User
{
var name : String
init?(name : String)
{
//必须先对name实例存储属性设置初始值,然后才能触发构造失败
self.name = ""
如果传入的name参数为空字符串,构造失败,返回nil
if name.isEmpty
{
return nil
}
self.name = name
}
}
上面代码中,如果将self.name = ""去掉,将会导致编译错误。如果希望在去掉的情况下不报错,也可以将name声明为可选类型: var name : String!
7.可能失败的构造器的传播:类、结构体、枚举的构造器可以横向调用该类型中另一个可能失败的构造器,类似地,子类的可能失败的构造器也可以向上调用父类的可能失败的构造器。
8. 可能失败的构造器可以调用同一个类中的普通构造器,反过来,普通构造器不能调用同一个类中可能失败的构造器。但是在结构体中,普通构造器却可以调用同一个结构体中可能失败的构造器。
9. 由于可能失败的构造器会产生传播,因此当可能失败的构造器在构造过程失败之后,这种失败行为会立即阻止原来构造器代码继续向下执行。
10. 举个栗子:
class Student : User
{
var grade : Int!
init!(name : String, grade : Int)
{
//调用父类的可能失败的构造器
super.init(name : name)
//如果grade小于1,则使用return nil触发构造失败
if grade < 1
{
return nil
}
self.grade = grade
}
}
let s1 = Student(name : "", grade : 3)
print(s1 == nil)
因为name为空,所以在super.init处返回了nil,因此super.init之后的所有代码都不会被执行。
11. 重写可能失败的构造器:子类可以重写父类的可能失败的构造器,子类既可以用可能失败的构造器,也可以用普通的构造器重写父类的可能失败的构造器----这样可以保证被重写后的构造器不可能失败。
12. 注意:子类的普通构造器可以重写父类的可能失败的构造器,但子类的普通构造器不能向上调用父类的可能失败的构造器。对于类的构造器而言,普通构造器永远不能调用可能失败的构造器,不管是父类的可能失败的构造器还是同一个类中可能失败的构造器。
13. Swift中运行在父类构造器前面添加required关键字,该关键字用于声明所有子类必须包含该required构造器。
14. 父类中声明的required构造器既可以是指定构造器,也可以是便利构造器。
15. required关键字 ***只***要求所有子类必须包含required构造器,至于子类是编码实现,还是继承得到的,Swift并不关心,至于子类采用指定构造器还是便利构造器,Swift也不关心。
16. 举个栗子:
class Fruit
{
var name : String
var weight : Double
//定义required的指定构造器
required inti(name : String)
{
self.name = name
self.weight = 0.0
}
//定义required的便利构造器
required convenience init(name : String, weight : Double)
{
self.init(name)
self.weight = weight
}
}
class Apple : Fruit
{
var color : String
//重写父类的required的便利构造器
required init(name : String, weight : Doublee)
{
self.color = "pink"
super.init(name : name)
}
init(color : String)
{
self.color = color
super.init(name : "")
}
//重写父类的required的指定构造器
//虽然此处属于构造器重写,但无须添加override
required convenience init(name : String)
{
self.init(color : "aaa"
super.name = name
}
}
//该类将会继承得到父类的required构造器
class Grape : Fruit
{
var sugarRate : Double = 0.45
}
因为Grape中没有定义任何指定构造器,因此,它将会得到父类的所有指定构造器和便利构造器。
17. 析构器:在实例被销毁之前,程序可能需要释放一些物理资源(并非释放内存,内存释放由系统自动完成),比如关闭文件、断开网络链接等等,此时可以借助于析构器来实现。
18. 析构器是一个名为deinit的函数(也无须用func关键字)析构器deinit没有返回值,也没有参数(甚至不能有圆括号)因此不能重载它。
19. 析构器在实例释放之前由系统自动调用,因此不要主动调用析构器。子类自动继承父类的析构器,如果子类实现了自己的析构器,子类析构器执行结束时将自动调用父类的析构器。无论如何,子类析构器一定会调用父类析构器。
20. 由于只有等到析构器被调用完成后,该实例才会被被销毁,因此析构器可以访问该实例的所有实例存储属性,或者根据这些属性来关闭资源。
21.举个栗子:
class Fruit
{
var name : String
var weight : Double
init(name : String)
{
self.name = name
self.weight = 0.0
}
deinit
{
print("in Fruit -- this object will be destoryed")
}
}
class Apple : Fruit
{
var color : String
init(name : String, weight : Double, color : String)
{
self.color = color
super.init(name : name)
}
deinit
{
print("in Apple -- this object will be destoryed")
}
}
注意:应该使用swiftc命令编译该程序,然后再运行,才可以看到预想的结果,再Palyground中运行该程序将看不到该效果。因为:Playground只是一个临时性的练习场所。