`
啸笑天
  • 浏览: 3466400 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

IOS中的block和retain cycle

 
阅读更多

retain cycle 的产生

 

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

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

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

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

头文件中:

 

Objective-c代码   收藏代码
  1. @property (nonatomic,retain) NSObject *obj;  

 在.m文件里:

 

Objective-c代码   收藏代码
  1. - (void)dealloc{  
  2.     [obj release];  
  3.     [super dealloc];  
  4. }  

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

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

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

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

Objective-c代码   收藏代码
  1. @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本身也可以看做一个对象,也存在生命周期,也可以被持有,所以当这种情况出现的时候,我们该注意了,比如:

 

Objective-c代码   收藏代码
  1. DoSomethingManager *manager = [[DoSomethingManager alloc] init];  
  2. manager.complete = ^{  
  3.     //...complete actions  
  4.     [manager otherAction];  
  5.     [manager release];  
  6. };  

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

 

Objective-c代码   收藏代码
  1. DoSomethingManager *manager = [[DoSomethingManager alloc] init];  
  2. manager.complete = ^{  
  3.     //...complete actions  
  4.     [manager otherAction];  
  5.     manager.complete = nil;  
  6.     [manager release];  
  7. };  

manager的complete被设置为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

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

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

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

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

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

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

 

Objective-c代码   收藏代码
  1. DoSomethingManager *manager = [[DoSomethingManager alloc] init];  
  2. manager.complete = ^{  
  3.     //...complete actions  
  4.     [manager otherAction];  
  5.     manager.complete = nil;  
  6. };  

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

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

 

Objective-c代码   收藏代码
  1. __block DoSomethingManager *manager = [[DoSomethingManager alloc] init];  
  2. manager.complete = ^{  
  3.     //...complete actions  
  4.     [manager otherAction];  
  5.     manager = nil;  
  6. };  

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

或者这么写:

 

Objective-c代码   收藏代码
  1. __block DoSomethingManager *manager = [[DoSomethingManager alloc] init];  
  2. DoSomethingManager __week *weekmanager = manager;  
  3. manager.complete = ^{  
  4.     //...complete actions  
  5.     [weekmanager otherAction];  
  6. };  

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

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

 

 

thx:http://lc-wangchao.iteye.com/blog/1596463

另参见 http://justsee.iteye.com/blog/1752091

分享到:
评论
1 楼 jiangfullll 2014-03-31  
好文啊!   

相关推荐

    iOS Block使用教程

    在iOS开发中,Block是一种强大的编程工具,它允许我们在代码中定义匿名函数或者闭包,使得函数能够作为参数传递,也可以直接在其他函数内部定义和使用。熟练掌握Block的使用对于提升iOS应用的代码质量、可读性和效率...

    ios-block回调 仅仅是回调.zip

    但是,需要注意Block可能导致循环引用(retain cycle),特别是在强引用Block的情况下。为了避免这种情况,可以使用弱引用self(__weak self)或者使用NSAutoReleasePool。 总之,"ios-block回调 仅仅是回调.zip"这...

    一篇文章让你看懂IOS中的block为何再也不需要WeakSelf弱引用

    然而,使用Block时常常会遇到一个问题,即可能导致循环引用(retain cycle),进而引发内存泄漏。通常,我们通过使用`__weak`关键字来打破这种循环引用,但这篇文章提出了一个观点:在某些特定情况下,我们可能不再...

    iOS中block变量捕获原理详析

    然而,需要注意的是,Block捕获变量可能会导致强引用循环(retain cycle),尤其是在Block内部持有对象实例时。为了避免这种情况,可以使用弱引用(`__weak`)或使用`__unsafe_unretained`关键字来弱化对象引用。 ...

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

    然而,如果不正确地使用Block,可能会导致循环引用(retain cycle),进而引发内存泄露。本文将深入探讨Block引起的循环引用问题,提供示例并分享解决方案。 一、什么是循环引用? 循环引用是指两个或多个对象互相...

    iOS开发实习面试题目

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

    block测试代码

    在iOS开发中,Block是一种强大的编程工具,它允许我们在代码中定义局部的匿名函数,能够直接操作上下文中的变量,简化了回调和异步处理。本文将深入探讨Block的基础知识,以及如何在实际项目中使用Block进行测试。 ...

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

    在iOS面试中,面对关于Block导致循环引用的问题,你需要展现出深度理解和实践经验,而不仅仅是复述常见解决方案。首先,理解循环引用的基本概念至关重要:当两个对象互相强引用时,导致它们都不能被正确释放,这就...

    《Ios组件与框架-iOS SDK 高级特性刨析》代码 全部之ARC部分

    在传统的手动引用计数中,程序员需要显式地调用`retain`、`release`和`autorelease`方法来控制对象的生命周期。而ARC由编译器自动插入这些调用,确保了对象在不再被使用时会被正确地释放,从而避免了内存泄漏和过早...

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

    它在Objective-C和Swift中自动跟踪并管理应用程序的内存使用,允许开发者不再手动调用`retain`和`release`方法。这样做的好处是可以降低程序崩溃和内存泄漏的风险,减轻开发者的负担,并提高程序的性能和可预测性。...

    ios考试面试题

    面试官可能会问及循环引用(Retain Cycle)的问题及解决办法,如使用弱引用或Block的__weak关键字。 4. **多线程** - GCD(Grand Central Dispatch)是苹果提供的多线程解决方案,理解队列(Dispatch Queue)类型...

    iOS面试题 2017年12月30日11-16-431

    5. **retain cycle例子**:例如,一个对象A持有一个Block,Block内部又引用了对象A,形成循环引用,如果不解除,会导致对象A无法释放。 6. **+(void)load**:在类加载时执行,一般用于静态初始化。 **+(void)...

    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...

    通过Runtime监测循环引用(Facebook出品).zip

    在iOS开发中,循环引用(Retain Cycle)是一个常见的内存管理问题,它可能导致对象无法被正确释放,从而引发内存泄漏。Facebook开发的FBRetainCycleDetector是一个强大的工具,旨在帮助开发者在运行时检测并解决这类...

    帮助你找到循环引用的问题的调试工具 iOS

    在iOS开发过程中,循环引用(Retain Cycle)是常见的内存管理问题,可能导致应用程序消耗过多内存,甚至引发崩溃。本文将详细介绍一款由作者yyued开发的名为CRChecker的调试工具,它专为解决这一问题而设计。 **...

    XXXTimer:不会引起对象循环引用, 并且在dealloc的时候, 自动停止的NSTimer~

    然而,如果不正确地使用NSTimer,可能会导致对象间的循环引用(retain cycle),进而引发内存泄漏问题。XXXTimer就是为了解决这个问题而设计的,它提供了一种更安全、智能的方式来管理NSTimer,确保在对象被释放...

Global site tag (gtag.js) - Google Analytics