GeekBand·iOS--Retain Cycle(引用循环)那些事

来源:互联网 发布:数据库物理结构包括 编辑:程序博客网 时间:2024/05/15 23:53

Retain Cycle:

这是一张Retain Cycle的示意图

Retain Cycle是如何形成的呢?

我们知道当一个父对象(主动方)持有子对象(被动方)时,子对象会随着父对象的消亡而消亡
但是假若两个对象互为父对象呢?会如何?
对,这样子,一个retain cycle就形成了,当然,互为父对象的引用都需要是强引用。

举个简单的例子:
就比如在一般的delegate情况下,一般都是子对象将delegate设为父对象,所以为了防止引用循环,delegate通常都是弱引用(当然也有不一般的情况,delegate的对象不是父对象,这种情况下设delegate为强引用没有问题,但由于delegate不可能是子对象,所以生命周期可以控制得比委托代理的对象的生命周期要长,哪怕是短也没问题,因为delegate是一个弱引用,而弱引用指针会自动置nil,至于能否在调用delegate方法时确定delegate对象依然存在需要靠程序员自己来控制delegate对象的生命周期了)

看不懂?让我用代码来举个更简单例子(MRR——Mutual Retain Release环境下):

这里写图片描述
第一个测试类
这里写图片描述
第二个测试类
这里写图片描述
这样,引用循环就形成了,造成的结果就是,子对象对父对象的引用无法再取得,导致两个对象被隐式引用,最后两个test实例的引用计数都是1,最后无法释放。
程序输出结果:
这里写图片描述

但是其实,这里有个小问题,苹果官网说了,retain count不能作为一个判断对象是否释放的标准。

所以这里,最好的用来判断对象是否释放的方法,是判断对象是否调用了自身的dealloc方法,在dealloc方法中添加log或者加断点可以轻易地发现,对象并没有调用dealloc。

如何解决Retain Cycle?

将子对象(被动方)对另一父对象——当然可以是自己,代码如下:

@interface Obj : NSObject@property(strong, nonatomic) id someObj;@endint main(){    Obj *obj = [[Obj alloc] init];    obj.someObj = obj;    return 1;}

在父对象生命周期结束(获取不到父对象的引用)前将子对象置nil或者release。

Block造成的引用循环

在编码中,由于block可以引用外部环境,于是我们对block的使用不慎,也可能导致引用循环,下面让我来分析一下在MRR以及ARC环境下,由block所引起引用循环

MRR

假设我有一个manager 实例,实例中有一个block的属性
block运行时,会隐性retain它所用到的变量
代码如下:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{     //...complete actions        [manager otherAction];        [manager release];};

这样子就造成 manager 和它的complete block相互持有,导致引用循环。

正确的做法应该是,在block中把manager里的complete block置nil,再释放自己,就可以破坏引用循环。
代码如下:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{     //...complete actions        [manager otherAction];        manager.complete = nil;     [manager release];};

ARC

在ARC中,在block中也会引入retain cycle,只是解决方法除了上面所提到的办法外,还有别的方法解决以及预防block中的retain cycle
比如:
在ARC中,引入了三个新的所有权修饰符

1. __strong : 修饰的变量会自动被retain一次,并且在block中也会被retain一次2. __unsafe_unretained:修饰的变量不会被retain,但在block运行中无法控制它的生命周期,可能block在运行过程中,它已经被释放了,留下一个野指针3. __weak:修饰的变量一样不会被retain,它修饰的变量的生命周期同样无法被block控制,但是好在它修饰的指针指向的内存在被系统释放后,它会置nil,这样就会安全许多。

在这里提一下,id类型和对象类型的所有权修饰符默认为__strong修饰符。即我们随手创建的对象,只要不特别声明,就是一个强引用。

事实上,还有两个所有权修饰符——即:

  1. __autoreleasing,但是这个修饰符非本文所要讨论的范围。
  2. __block,等下我们再来讨论这个修饰符

说回解决上述问题的retain cycle的新方法(ARC中):

DoSomethingManager *manager = [[DoSomethingManager alloc] init];// manager actions__weak DoSomethingManager *weakManager = manager;manager.complete = ^{     //...complete actions        [weakManager otherAction];   };

接下来我们来看一个新的解决block中的引用循环的办法吧!(用到__block关键字)

__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];manager.complete = ^{     //...complete actions        [manager otherAction];        manager = nil;};

如果不用ARC,manager不会在block中被retain,但是在ARC中,情况就有点复杂了。由于__block变量保存更为底层的地址,那么,当__block变量指向别的对象的时候,引发的情况就是,block不再对原来的变量负责,导致之前的对象被release掉,而retain cycle就被破坏了。

下面附上两篇关于retain cycle的好文:

  1. http://www.csdn.net/article/2015-05-27/2824782-demystifying-retain-cycles-in-arc
  2. http://swifter.tips/retain-cycle/
0 0