`

OC 循环引用

 
阅读更多

简单一点的说是:

     比如在main函数中创建了两个类的对象A和B,现在引用计数都是1。现在让A和B互相引用,现在两个对象的引用计数都增加了1,都变成了2。
      现在执行 [A release]; [B  release]; 此时创建对象的main函数已经释放了自己对对象的所有权,但是此时A和B的引用计数都还是1.因为他们互相引用了。
      这时你发现A和B将无法释放,因为要想释放A必须先释放B,在B的dealloc方法中再释放A。同理,要想释放B必须先释放A,在A的dealloc方法中再释放B。所以这两个对象将一直存在在内存中而不释放。这就是所谓的循环引用的问题。
      要想解决这个问题,一般的方法可以将引用的属性设置为assign,而不是retain来处理。

 

首先说下什么是循环引用,循环引用是指两个对象互相retain对方,通过OBJC的release是无法销毁这两个对象的。更严重的是,如果几个对象间接相互引用,比如A<-B,B<-C,C<-A,那么A、B、C都无法通过release释放。
例如下面的引用关系:
• 对象a创建并引用到了对象b.
• 对象b创建并引用到了对象c.
• 对象c创建并引用到了对象b.
这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。
这 种情况,必须打断循环引用,通过其他规则来维护引用关系。

引用链条中只要有一环发生循环引用,就会导致内存泄露。工具难以查出,因为符合使用约定。

遗憾的是相互引用十分常见,对于界面来说有时是必要的,特别是OBJC大量使用委托模式进行回调。因此OBJC也建议委托最好不进行retain,而改用 assign,以避免循环引用。不过一些异步调用,多线程使用是很难通过此种方式解决的,容易出现野指针。所以要解决循环引用导致的内存泄露需要从根源理 解泄露出现的原因,而不是单纯禁止使用。

循环引用问题,是引用计数所引入的问题。因此使用引用计数作为数据管理的架构都会出现类似问题。JAVA、C#的垃圾回收机制都有专门的算法来解决此问 题。引数管理的数据都是共享关系,相互引用就是相互共享,这种内存泄露是符合共享逻辑的。但实际情况中,当出现相互引用,往往存在依赖关系。一个引用为拥 有,而另一个仅为回调。回调引用依赖拥有引用。引数管理不能直接描述依赖关系,因此需要手工打断。

现假设B处由于某种原因使C被释放掉,比如线程结束或是Cancel操作,则此时在由root处释放A时,则全部释放。

所以不是只要循环引用就会内存泄露,通过打断解决内存泄露是有效的,因为打断补充了相互引用的依赖逻辑。但要注意,如果出于省事仅将打断代码放置在dealloc中,则仍然会出现内存泄露。原 因在于release字面意思是释放的意思,但这里的释放不是内存的最终释放而是所有权的释放。调用release是分不清内存释放还是仅仅减了一个计 数。循环引用时,存在于dealloc中的打断代码,将永远不会通过release调用。因此要解决循环引用,还要注意不要将打断代码放置在 dealloc中,而要在使用完毕后立即打断,dealloc的打断只能作为象征性防护。

总结:

防止循环引用导致的内存泄露,基本上有两种方法。

1)如果其中一个对象的生命周期覆盖另一个对象,则采用OBJC推荐的策略,短周期对象保持长周期对象使用assign。

2)如果相互引用的对象其生命周期不能覆盖对方,则必须在各自生命周期的终点处打断对方,打断代码不能依赖在dealloc时打断。比如对于UI对象可通过事件处打断,线程对象于Main结尾处打断。只要有一处打断被执行就不会产生内存泄露。

此外还有另一种解决方法——利用指针的指针建立依赖引用机制。

这里提供一下思路:引用双方只assign不retain,双方再额外存储对方引用的指针,当一方release时顺便将自身在对方的引用清空,以达到通知连接断开的目的。可将这种引用关系封装为一个类简化使用。

优点:避免循环引用导致内存泄露,即便在release处释放连接也不会引起内存泄露。

缺点:引用返回的是id类型,因此使用时需要类型转换。

另外编程时还要注意非预期的循环引用。比如OBJC库中经常使用字典作为参数的保存位置,而字典使用retain保存对象。如果A保持字典,字典中又保持A,或者保持B,但B保持A,则有可能产生内存泄露。

总之循环引用引起内存泄露,是需要在OBJC中额外注意的。C/C++不会产生此问题,因为引用需要手工维护。JAVA、C#中由于垃圾回收机制,也无需刻意注意。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics