`
923723914
  • 浏览: 644530 次
文章分类
社区版块
存档分类
最新评论

retain cycle

 
阅读更多

retain cycle 的产生

说到retain cycle,首先要提一下Objective-C的内存管理机制。


作为C语言的超集,Objective-C延续了C语言中手动管理内存的方式,但是区别于C++的极其非人道的内存管理,Objective-C提出了一些机制来减少内存管理的难度。 比如:内存计数。


Objective-C中,凡是继承自NSObject的类都提供了两种方法,retainrelease。当我们调用一个对象的retain时,这个对象的内存计数加1,反之,当我们调用release时, 对象的内存计数减1,只有当对象内存计数为0时,这个对象才真正会被释放,此时,对象的delloc方法会被调用来做些内存回收前的工作。


内存计数机制的好处在于我们可以明确分配一个使用权。比如,当一个对象A要使用另外一个对象B的时候,Aretain B一次以表示A使用B,而当B被使用完毕之后,A 调用Brelease方法来放弃使用权。这样,一个对象可以被多个其他对象使用。而作为使用它的对象,也不必关心自己之外 被使用对象的使用情况(内存方面)。一般来讲,对于类的成员变量,retainrelease分别发生在赋值和自身释放的时候,这就是Obj-C程序中的经典写法:


头文件中:


@property (nonatomic,retain) NSObject *obj;

.m文件里:


- (void)dealloc{ [obj release]; [super dealloc];}

OK,这种方式可以很容易地管理内存,但是仍存在这一个问题,这就是retain cycle


Retain cycle,翻译成中文大概叫保留环吧。既然父对象持有子对象,而子对象会随父对象释放而释放,那么,如果两个对象相互为父对象怎么办?


比如AB两个对象,A持有BB同时也持有A,按照上面的规则,A只有B释放之后才有可能释放,同样B只有A释放后才可能释放,当双方都在等待对方释放的时候, retain cycle就形成了,结果是,两个对象都永远不会被释放,最终内存泄露。


retain cycle使你编程的时候不得不注意一些问题。例如,要么尽量保持子对象引用父对象的时候使用弱引用,也就是assign,比如


@property (nonatomic,assign) NSObject *parent;

要么及时地将造成retain cycle中的一个变量设置为nil,将环break掉。如果注意点,这并不是什么特别大的问题。


嗯,注意点确实不是什么问题,但是当IOS 4.0只后,block的出现,使你更需要更为谨慎。


block与内存管理

block就是一段可以灵活使用的代码,你可以把它当变量传递,赋值,甚至可以把它声明到函数体里,更灵活的是你可以在里面引用外部的环境。 最后一条使得block要有更多的考虑,既然block可以引用外部环境,那如何保证block被调用的时候当时的环境变量不被释放呢?(block调用的时机可能是随意的)


答案就是,被block引用的变量都会被自动retain一次,这样的话至少可以保证我们的调用是有效的。


说到这里你能想到什么吗?对,还是retain cycle。因为block中的retain是隐式的,所以极易出现retain cycle的问题。


因为block本身也可以看做一个对象,也存在生命周期,也可以被持有,所以当这种情况出现的时候,我们该注意了,比如:


DoSomethingManager *manager = [[DoSomethingManager alloc] init];

manager.complete = ^{ //...complete actions

[manager otherAction];

[manager release];};

retain cycle 就这么形成了,即使调用了releasemanager也不会释放,因为managerblock相互持有了。为了解除retain cycle的话,我们可以这样写:

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

[manager otherAction];

manager.complete = nil;

[manager release];};

managercomplete被设置为nil,如此一来retain cycle也被破坏掉,前提是你确实不需要再次回调block了。

本来写到这里就算完了,但是新世纪总有新的挑战,这就在于在Apple有推出了一种新的技术 ARC

ARC retain cycle

ARC (Auto Reference Counting), 翻译为自动引用计数,是Apple为了进一步简化内存管理来推出的技术。虽然为自动内存管理而生,但却并算不上真正的自动管理。 这是因为ARC是一种编译期的技术,它所做的是自动识别你的代码并转换成retain/release的形式,在这个层面上来看,ARC无非是简化了代码的书写,并提供了部分性能上的优化, 而并不像Java之类的语言可以完全把垃圾回收抛之脑后(基本上)。关于ARC的细节可以看下面的网址:

http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

下面我们主要谈下ARCretain cycle的问题。

ARC中,变量可以用三个关键字修饰:

__strong 赋值给这个变量的对象会自动被retain一次,如果在block中引用它,block也会retain它一次。__unsafe_unretained 赋值给这个变量不会被retain,也就是说被他修饰的变量的存在不能保证持有对象的可靠性,它可能已经被释放了,而且留下了一个不安全的指针。不会被block retain __week:类似于__unsafe_unretained,只是如果所持有的对象被释放后,变量会自动被设置为nil,这样更安全些,不过只在IOS5.0以上的系统支持,同样不会被block retain

另外我们也可以用 __block 关键字修饰一个变量,表示这个变量能在block中被修改(值修改,而不是修改对象中的某一个属性,可以理解为修改指针的指向)。会被自动retain

于其他变量不同的是被 __block 修饰的变量在块中保存的是变量的地址。(其他为变量的值)

首先,上面的代码你现在可以这么写:

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

[manager otherAction];

manager.complete = nil;};

没什么问题,只是去掉了ARC中禁止的release

当然,我们也可以这么写。

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

[manager otherAction];

manager = nil;};

如果不用ARCmanager不会在block中被retain,但是采用了ARC就有些复杂了。blockretain manager变量,但是,由于__block变量保存更为底层的变量地址, 因此当此变量被指向其他对象时,block便不对原来的对象负责,引发的结果就是之前对象被release掉,retain cycle被破坏。

或者这么写:

__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];DoSomethingManager __week *weekmanager = manager;manager.complete = ^{ //...complete actions

[weekmanager otherAction];};

上面的__week也可以用 __unsafe_unretained 替代,但是 __week 更安全些,虽然它不支持IOS5.0以下的系统。

__week 或者 __unsafe_unretained 修饰的变量不会被block retain,所以不会形成retain cycle,但是小心,保证你的对象不会在complete之前被释放,否则会得到你意向不到的结果。

分享到:
评论

相关推荐

    ARC中RetainCycle揭秘

    不幸的是ARC没有循环引用检测器,因此很容易出现RetainCycle现象,从而迫使开发者在编码时要采取特殊的预防措施。ARC中的RetainCycle就像日本B级恐怖电影一样。开始使用Cocoa或CocoaTouch做开发时,你甚至不会在意它...

    iOS开发实习面试题目

    你⽤用哪些办法实现过多线程? GCD和NSOperationQueue的⽐比较,各⾃自优缺点是什么,平时你是怎么使⽤用的? weak,assign,strong的意义和...怎样破除block产⽣生的retain cycle? 为什么不能在getter⾥里使⽤用点语法?

    ios-RJBadgeKit.zip

    // Use [observer doSomething] instead of [self doSomething] to avoid retain cycle in block // key path -> info[RJBadgePathKey] : badgeContoller所observe的路径 // badge status -> info...

    关于一些 iOS 面试问题的解答1

    1. 什么是 ARC 4. 使用 nonatomic 一定是线程安全的吗 5. 描述一个你遇到过的 retain cycle 例子. 7. 为什么其他语言里叫函

    iOS面试中如何优雅回答Block导致循环引用的问题

    如果面试官问你开发中是否遇到过retain cycle,你如果说没遇到过,估计已经很难跟面试官继续友好的沟通下去了。 但是这个问题怎么回答呢,网络上千篇一律的答案–>使用Block的时候遇到过,使用__weakSelf 代替 self ...

    HttpRequestSwift:基于NSURLSession 封装的网络请求 使用简单 支持多种请求方式

    HttpRequestSwift 基于NSURLSession 封装的网络请求 使用简单 支持多种...block会给内部所有的对象引用计数加一,这一方面会带来潜在的retain cycle,不过我们可以通过Weak Self的手段解决。另一方面比较重要就是,它会

    ios_learning

    @作者:徐迪璐@如果您对回购有任何疑问或问题,请随时与我联系: ios_learning ... 在该项目中,开发了一个微型演示程序来显示retainCycle问题。 测试 该项目是在校招学习计划中第2周任务的解决方案。

    block的循环引用导致的内存泄露的示例及解决办法

    block的循环引用导致的内存泄露的示例及解决办法

    objective-c启用ARC时的内存管理

    在objective-c中,内存的引用计数一直是一个让人比较...似乎ARC的出现只是让我们解放了双手,由于底层实现依然依赖引用计数,所以开启ARC后,只有对引用计数机制更加了解,才能避免Cycle Retain、Crash等问题的出现。

    httpd2.2.12.tar

    release cycle, while allowing forward progress to occur without concern for breaking the stable branch. This document explains the rationale between the two versions and their behavior. STABLE ...

    iOS 9 Programming Fundamentals with Swift (非epub转pdf)

    the nib-loading mechanism, Cocoa patterns of delegation and notification, and retain cycles, wonder no longer — I didn’t explain them there because I do explain them here. Table of Contents Part I...

Global site tag (gtag.js) - Google Analytics