- Swift内存管理概述
- 强引用循环
- 打破强引用循环
- 闭包中的强引用循环
Swift内存管理概述
-
Swift中的ARC内存管理是对引用类型的管理,即对类所创建的对象采用ARC管理。而对于值类型,如整型、浮点型、布尔型、字符串、元组、集合、枚举和结构体等,是由处理器自动管理的,程序员不需要管理它们的内存。
-
ARC内存管理和值类型内存管理有一定的区别。虽然两者都不需要程序员管理,但本质上是有区别的,ARC和MRC一样都是针对引用类型的管理,引用类型与Objective-C中的对象指针类型一样,它们的内存分配区域是在“ 堆”上的,需要人为管理。而值类型内存分配区域是在“栈”上的,由处理器管理,不需要人为管理。
引用计数
每个Swift类创建的对象都有一个内部计数器,这个计数器跟踪对象的引用次数,称为引用计数(Reference Count, 简称RC)。
class Employee {
var no: Int
var name: String
var job: String
var salary: Double
init(no: Int, name: String, job: String, salary: Double) {
self.no = no
self.name = name
self.job = job
self.salary = salary
print("员工\(name) 已经构造成功。")
}
deinit {
print("员工\(name) 已经析构成功。")
}
}
var ref1: Employee?
var ref2: Employee?
var ref3: Employee?
ref1 = Employee(no: 7698, name: "Lucy", job: "teacher", salary: 4000) //RC = 1
ref2 = ref1
ref3 = ref1
ref1 = nil // RC = 2
ref2 = nil // RC = 1
ref3 = nil // RC = 0
强引用循环
当两个对象的存储属性互相引用对方的时候,一个对象释放的前提是对方先释放,另一对象释放的前提也是对方先释放,这样就会导致类似于“死锁”的状态,最后谁都不能释放,导致内存泄漏。这种现象就是强引用循环。
如下图:Employee 和Department互相引用
image.png
class Employee {
var no: Int
var name: String
var job: String
var salary: Double
var dept: Department?
init(no: Int, name: String, job: String, salary: Double) {
self.no = no
self.name = name
self.job = job
self.salary = salary
print("员工\(name) 已经构造成功。")
}
deinit {
print("员工\(name) 已经析构成功。")
}
}
class Department {
var no: Int = 0
var name: String = ""
var manager: Employee?
init(no: Int, name: String) {
self.no = no
self.name = name
print("部门\(name) 已经构造成功。")
}
deinit {
print("部门\(name) 已经析构成功。")
}
}
var emp: Employee?
var dept: Department?
emp = Employee(no: 3041, name: "Lucy", job: "teacher", salary: 5000) //RC = 1
dept = Department(no: 28, name: "Lily") // RC = 1
emp!.dept = dept //RC = 2
dept!.manager = emp // RC = 2
emp = nil //RC = 1
dept = nil // RC = 1
如上代码:类Employee中的deinit方法和Department中的deinit方法都没有被调用,说明对象emp和对象dept都没有被释放掉。
emp 与dept对象之前建立关系
emp 和 dept强引用断开
注:对象释放的前提是没有指向它的强引用,也就是它的引用计数为0。例如:指向Employee对象的强引用原本有两个(1#和4#),也就是引用计数为2;当emp = nil 断开1#强引用,引用计数为1;由于强引用循环4#强引用一直不能断开,引用计数不能为0,导致Employee对象不能释放。
Employee对象和Department对象都没有被释放,这就是强引用循环,会导致内存泄漏。
打破强引用循环
强引用循环的危害非常大,所以我们要打破强引用循环,打破强引用循环的方法有两种
- 弱引用( weak reference)
- 无主引用 (unowned reference)
弱引用
弱引用允许循环引用中的一个对象不采用强引用方式引用另一个对象,这样就不会引起强引用循环问题。弱引用适合于引用对象可以没有值的情况,因为弱引用可以没有值,我们必须将每一个弱引用声明为可选类型,使用关键字Weak声明为弱引用。
如下图:Employee对Department的引用是弱引用(极端情况下员工可以没有部门)
image.png运行的结果:如下图
两个构造函数和两个析构函数都走到了,说明这两个对象都释放了
image.png
emp 与dept对象之前建立关系
emp 和 dept弱引用断开
image.png解释:
1)emp = nil,emp被赋予空值,1#引用关系被打破,此时Employee对象的引用计数为1,即RC=1,因为还有4#引用,4#引用指向Employee对象,4#引用是强引用。所以Employee对象暂时还不能被释放。
2)dept = nil, dept被赋予空值,2#引用关系被打破,此时Department对象的引用计数为0, 即RC = 0。
3)之所以Department对象的RC = 0,是因为没有指向Department对象的强引用。3#引用是弱引用,弱引用不占用RC,RC不计数,RC不对弱引用计数,只对强引用计数。此时Department对象先被释放。
4)Department对象被释放之后,4#强引用关系被打破,即指向Employee对象的引用没有了,紧接着Employee对象引用计数变为0,然后Employee对象被释放。
所以运行结果是部门对象先被释放,然后员工对象再被释放。
无主引用
无主引用与弱引用一样,允许循环引用中的一个对象不采用强引用方式引用另外一个对象,这样就不会引用循环问题。无主引用适用于引用对象永远有值的情况,它总是被定义为非可选类型,使用关键字unowned表示这是一个无主引用。
如下图:Department对Employee的引用是无主引用,值不能为空(部门必须有个领导)
image.png
运行的结果:如下图
两个构造函数和两个析构函数都走到了,说明这两个对象都释放了,并且员工先释放,部门后释放。
emp 和 dept 之间建立联系
截屏2020-07-01 上午12.30.03.png解释:
1)emp = nil,emp被赋予空值,1#引用关系被打破,此时Employee对象的引用计数为0,即RC=0,因为此时已经没有指向它的强引用了,4#是无主引用,无主引用不占用RC,RC不计数,RC不对无主引用计数,只对强引用计数。此时Employee对象先被释放。
2)dept = nil, dept被赋予空值,2#引用关系被打破,此时Department对象的引用计数为1, 即RC = 1, 因为此时还有指向Department对象的3#强引用。
3)Employee对象被释放后,3#关系被打破,即指向Department对象的引用没有了,紧接着Department对象引用计数变为0,然后Department对象被释放。
所以运行结果是员工对象再先被释放,然后部门对象再被释放。
闭包中的强引用循环
由于闭包本质上也是引用类型,因此也可能在闭包和上下文捕获变量(或常量)之间出现引用循环问题。
并不是所有的捕获变量(或常量)都会发生强引用循环问题,只有将闭包赋值给对象的某个属性,并且这个闭包体使用了该对象,才会产生闭包强引用循环。
示例代码:如下图
image.png
运行以上代码:结果如下图
image.png
没有调用析构函数,说明Employee对象并没有真正被释放。
闭包捕获列表来解决强引用循环语法
lazy var 闭包: <闭包参数列表> -><返回值类型> = {
[unowned 捕获对象] <闭包参数列表> -> <返回值类型> in
或 [weak 捕获对象] <闭包参数列表> -> <返回值类型> in
//闭包体
}
或
lazy var 闭包:() -> <返回值类型> = {
[unowned 捕获对象] in
或 [weak 捕获对象] in
//闭包体
}
捕获的对象有时可能为nil时,则将闭包内的捕获声明为弱引用
如果捕获的对象绝对不会为nil,那么应该采用无主引用。
代码如下:
image.png
运行后结果如图:
image.png
已经调用析构函数,说明Employee对象已经被释放。
也可以用下面的无主引用实现
image.png
注:
- 如果捕获的对象有时候可能为ni时,则将闭包内的捕获声明为弱引用。
- 如果捕获的对象绝对不会为nil,那么应该采用无主引用。
网友评论