循环引用与闭包逃逸
在一年前的一个文章里循环引用提到了在某下情况下可以在闭包中安全使用 self 而不引起循环引用的问题
当时的分析是错误的,确实没有引起循环引用,但是对于原因的定位是错误的
但是万幸当时知道了Block存在三种 StackBlock MallocBlock GlobalBlock 最近详细研究了一下。
Block类型 | 解释 |
---|---|
StackBlock | 函数盏内的Block,除了该Block存在的类对其进行了Retain之外,别的对象不会Retain它 |
MallocBlock | 内存中的Block,本身可以被其它对象持有,RetainCount不定 |
GlobalBlock | 全局Block,并不被特定的对象持有,相当于全局变量 |
Block的实质
以下面的Block为例子,根据Mach-O文件分析,其实质相当于一个对象的方法
1 |
|
将会在符号表中生成一个类似这个名字的函数
1 | 00000436 t -[Class someMethod] |
当我们使用A对象,调用当B对象当B中的 someFuncWithBlock 并且传入一个Block时,OC会在Runtime的过程中创建一个Block Object
闭包逃逸
Escaping
那么这个Block到底是 StackBlock 还是 MallocBlock 决定于 someFuncWithBlock 内部的实现,而内部实现决定了
1 | //StackBlock |
在 StackBlock 的实现中,Block的接收对象并没有对其进行任何Retain,直接进行执行
而 MallocBlock 则是对传入对象进行了 持有,对其Retain Count +1 此时Block的对象由Stack逃出(Escaping)到Malloc上,内存模型如下
- StackBlock
- MallocBlock
当Autorelease时
当前线程的Runloop执行完一次,调用AutoreleasePool对象执行了drain方法后,StackBlock由于引用计数为零,将会被释放,而MallocBlock由于引用计数不为零,则不会被释放
- StackBlock: Block释放–>A Retain -1 –>A释放–>B释放
- MallocBlock: Block无法释放–>A不会释放–>B不会释放–>Block无法释放
此时发现,如果在MallocBlock中调用了self,则会导致循环引用 Cycle Retain
结论
循环引用Retain Cycle是存在的,但是在StackBlock下可以安全的使用self,Swift中加强了这个概念,引入了@escaping来提醒码农不可以安全使用self
Swift中所有的Block默认都是 @unescaping 的,在Swift 3.0之后生效