80. Swift中的类没有默认的父类,如果在定义一个类的时候没有指定其父类,则这个类就是一个基础类,不继承任何其他类。
81. Swift中类的继承关系适用冒号来声明,和C#中一样。在子类中重写父类方法适用override关键字。在子类的重写方法中想要调用父类方法或属性,或者想要在初始化函数中调用父类的初始化函数,适用super关键字,对于父类的subscript,适用super[index]来调用。
82. 在子类中重写父类属性的时候,一定要指定属性名和类型,这样编译器才能确定父类中是否有一个相同的属性。并且如果父类的属性是只读的,子类的重写属性可以定义为读写属性,如果父类属性是读写的,子类的重写属性不能是只读的(即子类的属性限制要更少)。此外,还可以重载willSet和didSet方法。如果不想一个类、方法、属性、下标(subscript)被重写或继承,适用@final关键字来修饰其声明即可。
83. 在类的声明中定义属性时直接为其赋初始值或者在初始化函数中为其赋值,不会调用willSet和didSet方法。
84. Swift中类的初始化函数的参数和类的方法的参数类似,都默认其参数的内部名称和外部名称相同,只不过初始化函数的第一个参数也同样默认其外部名称,因此在调用的时候需要制定所有的参数名。如果想要忽略某些参数,则显式地制定外部名称为下划线“_”。
85. 如果一个结构体或者基类(没有父类)的所有属性都在声明的时候指定了默认值而没有定义初始化方法的话,则Swift会为其创建一个默认的初始化方法(不带参的)。
86. 对于可选类型的属性,可以不指定其默认值,可选类型的变量如果不指定默认值,其默认值为nil。
87. 在Swift中定义方法重载的时候,可以定义参数类型完全相同的重载,只要参数的外部名称不同即可,例如下面的定义就是有效的(如果去掉参数的“#”号,则会报出“函数重复定义”的编译错误:
class Demo {
func test(#p1 : Int) {
println(p1)
}
func test(#p2 : Int) {
println(p2)
}
}
88. Swift中类的初始化方法分为两类,一类叫指派初始化方法(designated initializer),另一类叫便利初始化方法(convenience initializer)。指派初始化方法通常用来初始化所有的类中定义的属性,并且调用合适的父类初始化方法进行初始化,指派初始化方法通常只有一个(也至少要有一个),但是不限于一个。便利初始化方法通过调用类中定义的某个指派初始化方法来初始化类的属性,对于某些属性定义其默认值,便利初始化方法可以为满足一些特定的初始化需要而创建,如果不需要他们完全可以不创建,便利初始化方法声明在init关键字前面加上convenience关键字即可。
89. Swift中的初始化分为两个阶段,第一个阶段保证所有用来存储值的属性必须都赋初值,第二阶段可以允许对属性进行更多的设置。
Swift中提供4个安全检查来保证两个初始化阶段的顺利执行:
·一个指派初始化函数必须保证在所有的类中引入的属性都初始化完成后再去调用父类的初始化方法。
·一个指派初始化函数必须保证在为继承来的属性赋值之前调用父类的初始化方法进行了初始化。如果不这样做,这个赋的新值会被父类的初始化方法覆盖掉。
·一个便利初始化方法在为任何属性赋值之前必须调用其他的初始化方法,如果不这样做,这个赋值操作会被指派初始化方法的赋值操作给覆盖掉。
·在初始化第一阶段完成之前,任何初始化方法都不能调用实例方法,读取任何实例属性或者指定和使用self值。
90. 可以使用闭包来为属性赋初始值(对于需要复杂一点的计算来赋值的属性),在闭包中,不能使用其他属性或者self,因为此时初始化的第一阶段尚未完成,例如:
class Calendar {
var monthDayCount : Int[] = {
varmonthDayCountTmp : Int[] = Array(count : 12, repeatedValue : 0)
for index in0...11 {
switch index {
case 0, 2, 4, 6, 7, 9, 11:
monthDayCountTmp[index] = 31
case 3, 5, 8, 10:
monthDayCountTmp[index] = 30
case 1:
monthDayCountTmp[index] = 28 //闰年就不算了先,只是举个例子
default:
break
}
}
returnmonthDayCountTmp
}()
}
var cal = Calendar()
println(cal.monthDayCount)
91. Swift中使用deinit关键字来定义析构函数,只有类可以定义析构函数。Swift会替我们完成大多数释放资源的工作(ARC),但是对于我们自己申请和使用的资源,例如我们在程序中打开一个文件,这种情况下就需要在析构函数中对其进行释放。析构函数定义不需要参数,因此没有用来定义参数的圆括号:
deinit {
//…
}
析构函数在对象的实例被释放的时候自动调用,不允许显式地调用,在析构函数执行的时候,资源仍然可以访问。
父类的析构函数在子类析构函数调用的时候自动调用。
92. 由于Swift中使用ARC来管理引用对象的释放,因此存在循环引用的关系,例如一个职员类和办公室类,职员类中定义一个属性用来引用他的办公室,办公室的负责人指向一个职员,这时引用计数永远也不会为0,因此会造成内存泄露。Swift提供了两种方法来解决循环引用,一种是使用弱引用(weak reference),一种是使用非所有引用(unowned reference)。当能够确定一个引用在某些时候可能为nil的时候,使用弱引用,当一个引用使用不会变为nil的时候,使用非所有引用。
93. 弱引用:在属性或变量声明的前面使用关键字weak来表明弱引用。弱引用必须用于变量(且一定是可选类型的),常量不可更改,因此不允许声明为弱引用。弱引用指向的对象可能会在过程中被释放掉(因为是弱引用,所以ARC并不会保存对象),这时可以通过检查引用变量是否为nil来判断(如果弱引用指向的对象被释放掉了,指向弱引用对象的变量会被置为nil)。
94. 非所有引用:在属性或变量声明的前面使用关键字unowned来表明非所有引用。非所有引用的变量不能被设置为nil,如果在执行过程中,非所有引用指向的对象被释放了,会触发执行异常,因此在编写过程中请确保在使用引用对象的时候它没有被释放掉。
95. 闭包引起的循环引用:
class HTMLElement {
let name: String
let text: String?
@lazy var asHTML: () -> String = {
if let text =self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
在定义asHTML属性的时候,由于闭包方法中引用了self对象,因此闭包方法会保留当前类的实例对象的一个强引用,而当前实例对象也会保留闭包方法的强引用,因此形成循环引用。使用捕获列表(capturelist)来处理这种情况。
96. 在闭包的参数列表前面定义它的捕获列表,捕获列表的定义由一系列的[weak/unowned,referenceObject]对儿组成,之间用逗号分隔开,referenceObject指代闭包中的引用对象。例如:
@lazy var someClosure: (Int, String) -> String = {
[unowned self] (index: Int, stringToProcess: String) ->String in
//…闭包方法
}
当闭包和闭包中的引用实例始终指向彼此的时候,通过unowned关键字来定义捕获列表项,二者会同时被释放掉。而如果闭包中的实例对象有可能变为nil的时候,使用weak来定义捕获列表项,当实例对象被释放掉的时候,引用变量会置为nil,此时可以在闭包中来判断对象是否被释放掉了。
因此我们修改上面例子中的asHTML方法来消除循环引用:
@lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return"<\(self.name)>\(text)</\(self.name)>"
} else {
return"<\(self.name) />"
}
}