十九. Swift内存管理
1. Swift提供了强大的内存管理机制:Swift通过自动引用计数(ARC)可以很好地管理对象的回收。大部分时候,程序无须关心Swift对象的回收,但在某些特殊情况下,Swift要求程序员进行一些内存管理的处理。
2. 只有引用类型变量所引用的对象才需要使用引用计数进行管理,对于枚举、结构体,他们是值类型,因此不需要使用引用计数进行管理。
3. ARC是一种非常优秀的内存管理技术,它的实现思路非常简单:当程序在内存中创建一个对象之后,ARC将会自动完成两件事情:
(1)ARC自动统计该对象被多少个引用变量所引用,这个值就被称为引用计数。简单地说,ARC相当于为每个对象额外增加了一个Int类型的属性,该属性总能正确地记录有多少个引用变量在引用该对象。
(2)每当一个对象的引用计数为0时,ARC会自动回收该对象。
4. 没有必要去了解ARC到底是如何统计的,使用ARC之后,程序甚至不允许直接访问对象的引用计数。
5. 强引用:大部分时候,ARC可以很好地处理程序中对象的内存回收,但如果两个对象之间存在双向关联:两个对象都使用存储属性互相引用对方的时候,此时两个对象的引用计数都等于1,但它们实际上并没有被真正的引用变量所引用,但ARC也无法回收它们。如果没有特殊说明,Swift中的引用变量都是强引用,此时两个对象形成了强引用循环。
6. 为了解决上面的强引用循环,必须有一方做出“让步”,允许对方先释放,这样问题就解决了。Swift为解决强引用循环提供了两种解决机制:弱引用和无主引用。
7.使用弱引用解决强引用循环:弱应用不会增加对方的引用计数,因此不会阻止ARC回收被引用的实例,这样就可以避免形成强引用循环。在定义属性的var关键字前面使用weak关键字即可定义弱引用。
8. 弱应用变量要求该应用变量 ***必须*** 允许被设为nil,也就是弱应用是可以没有值的,所以推荐使用可选类型来定义弱引用属性。
9. 弱应用只能声明为变量类型,因为该属性在允许期间内值有可能发生改变,因此弱引用决不能声明为常量。
10. 由于弱引用不会持有实例,因此即使弱引用存在,ARC也可能会销毁实例,并将弱引用变量赋值为nil,所以程序可以像检查其他可选类型的变量一样检查弱引用是否存在,这样就永远不会出现调用了被销毁实例的情形。
11. 使用弱引用非常简单,只要将相互引用的两方中的任意一方的关联属性使用weak修饰即可,举个栗子:
class Student
{
var name : String
var skill : String
//使用弱引用持有Teacher对象
weak var teacher : Teacher?
init(name : String, skill : String)
{
self.name = name
self.skill = skill
}
deinit{
print("teacher will be destoryed")
}
}
12. 使用无主引用解决强引用循环:无主引用也不会增加对方的引用计数。无主引用与弱引用的区别是:无主引用不允许接受nil(即应该始终有值),因此无主引用只能定义为非可选类型。在定义属性的var或let关键字之前添加unowned关键字即可定义无主引用。
13. 因为无主引用是非可选类型的,因此当使用使用无主引用时不需要强制解析,可以直接访问。但由于非可选类型的属性不能为nil,因此当ARC回收了引用变量属性所引用的实例之后,ARC无法将该引用变量属性赋值为nil
14. 当无主引用所引用的对象被销毁之后,无主引用变量并没有被赋值为nil,因此程序无法准确判断无主引用所引用的对象是否已经被销毁。如果程序试图通过无主引用去调用被销毁的实例,将会导致运行时错误。
15. 弱引用和无主引用的实例覆盖了两种常用的场景:
(1)如果两个实体中记录关联实体的属性都可能是nil,并有可能产生强引用循环,此时适合使用弱引用。
(2)如果两个实体中记录关联实体的属性一个是nil,另一个不能是nil,并有可能产生强引用循环,此时适合使用无主引用。
还有一种情况:两个属性都必须有值,且初始化后都不会为nil,并有可能产生强引用循环,此时要求一个类使用无主引用,另一个类使用隐式可选属性。
16. 闭包与对象的强引用循环:如果累的某个属性不是普通类型,而是函数类型,那么这个属性就有可能返回一个闭包。闭包会捕获传入其中的引用变量----如果程序将该对象本身传入了闭包,那么闭包本身就会捕获该对象,于是该对象就持有了闭包属性;反过来,闭包也持有了该对象,这样对象和闭包也形成了强引用循环。
17. 解决闭包与对象之间的强引用循环时,到底采用弱引用还是无主引用,也需要根据实际场景来选择。当闭包和捕获的实例总是相互引用,且总是同时销毁时,应该将闭包内捕获的实例定义为无主引用。相反的,当闭包捕获的引用变量有可能是nil时,将闭包捕获的引用变量定义为弱引用。弱引用必须是可选类型,当被引用的实例被销毁后,弱引用的变量会自动被赋值为nil。
18. Swift通过为闭包定义捕获列表来指定捕获变量应该采用弱引用,还是采用无主引用。语法格式如下:
{[unowned|weak 捕获变量](形参列表) ->返回值类型 in
//闭包执行体
}
如果闭包无须定义形参、返回值类型,则该闭包语法格式为:
{[unowned|weak 捕获变量] in
//闭包执行体
}
可以看出,捕获列表总是定义在闭包的第一行,且使用方括号括起来,每个捕获变量都需要unowned或者weak修饰。
19. 举个栗子:
class Student
{
var name : String
var age : Int
lazy var stuInfo : () -> String ={
//定义捕获列表,指定闭包中self引用变量是无主引用
[unowned self] in
"\(self.name),\(self.age)"
}
init(name : String, age : Int)
{
self.name = name
self.age = age
}
//定义析构器
deinit{
print("student will be deinit")
}
}
var stu : Student? = Student(name : "xiaoming", age : 12)
var info : (() -> String)? = stu!.stuInfo
stu = nil
info = nil
由于无主引用不会增加对方的引用计数,因此在程序中Student对象的计数为0,于是ARC将会先释放内存中的Student对象。当Student对象被释放之后,Student的属性就不复存在了,此时Student对象也就不再引用内存中的闭包了,于是闭包的引用计数也变为0,ARC会释放闭包。