Objective-C中的引用计数
导言
Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活,那就递增其引用计数;用完了之后,就递减其计数。计数为0,就表示没人关注此对象了,于是,就可以把它销毁。
从Mac OS X 10.8开始,“垃圾收集器”(garbage collector)已经正式废弃了,以Objective-C代码编写Mac OS X程序时不应再使用它,而iOS则从未支持过垃圾收集。因此,掌握引用计数机制对于学好Objective-C来说十分重要。Mac OS X程序已经不能再依赖垃圾收集器了,而iOS系统不支持此功能,将来也不会支持。
已经用过ARC的人可能会知道:所有与引用计数有关的方法都无法编译,然而现在先暂时忘掉这件事。那些方法确实无法用在ARC中,不过本文就是要从Objective-C的角度讲解引用计数,而ARC实际上也是一种引用计数机制,所以,还是要谈谈这些在开启ARC功能时不能直接调用的方法。
工作原理
在引用计数架构下,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。这在Objective-C中叫做“保留计数”(retain count),不过也可以叫“引用计数”(reference count)。NSObject协议声明了下面三个方法用于操作计数器,以递增或递减其值:
1)retain 递增保留计数。
2)release 递减保留计数。
3)autorelease 待稍后清理“自动释放池”(autorelease pool)时,再递减保留计数。
上图是对象创建及保留计数操作的效果图。
上图对象图中,ObjectB与ObjectC都引用了ObjectA。若ObjectB与ObjectC都不再使用ObjectA,则其保留计数降为0,于是便可摧毁了。还有其他对象想令ObjectB与ObjectC继续存活,而应用程序里又有另外一些对象想令那些对象继续存活。如果按“引用树”回溯,那么最终会发现一个“根对象”(root object)。在Mac OS X应用程序中,此对象是NSApplication对象;而在iOS应用程序中,则是UIApplication对象。两者都是应用程序启动时创建的单例。
下面这段代码有助于理解这些方法的用法:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:];
[array addObject:number];
[number release];
//do something with 'array' [array release];
由于代码中直接调用了release方法,所以在ARC下无法编译。在Objective-C中,调用alloc方法所返回的对象由调用者所拥有。也就是说,调用者已通过alloc方法表达了想令该对象继续存活下去的意愿。不过,这并不是说对象此时的保留计数就是1。在alloc或“initWithInt:”方法的代码实现中,也许还有其他对象也保留了此对象。绝不能说保留计数一定是某个值,只能说你所执行的操作的递增了该计数还是递减了该计数。
创建完数组后,把number对象加入其中。调用数组的“addObject:”方法时,数组也会在number上调用retain方法,以期继续保留此对象。这时,保留计数至少为2。接下来,代码不再需要number对象了,于是将其释放。现在的保留计数至少为1。这样就不能照常使用number变量了。调用release之后,已经无法保证所指的对象仍然存活。当然,根据本例中的代码,我们显然知道number对象在调用了release之后仍然存活,因为数组还在引用着它。然而绝不应该假设此对象一定存活,也就是说,不要像下面这样子编写代码:
NSNumber *number = [[NSNumber alloc] initWithInt:];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);
即便上述代码在本例中可以正常执行,也仍然不是个好办法。如果调用release之后,基于某些原因,其保留计数降至为0,那么number对象所占内存也许会回收,这样的话,再调用NSLog可能就将使程序崩溃了。为什么是“可能”,因为对象所占的内存在“解除分配”(deallocated)之后,只是放回“可用内存池”(avaiable pool)。如果执行NSLog时还尚未覆写对象内存,那么该对象仍然有效,这是程序不会崩溃。故,因过早释放对象而导致的bug很难调试。
为避免在不经意间使用了无效对象,一般调用完release之后都会清空指针。这就能保证不会出现可能指向无效对象的指针,这种指针通常称为“悬挂指针”(dangling pointer)。例如,可以这样编写代码来防止此情况发生:
NSNumber *number = [[NSNumber alloc] initWithInt:];
[array addObject:number];
[number release];
number = nil;
属性存取方法中的内存管理
如前所述,对象图由相互关联的对象所构成。刚才那个例子中的数组通过在其元素上调用retain方法来保留那些对象。不光数组,其他对象也可以保留别的对象,这一般通过访问“属性”来实现,而访问属性时,会用到相关实例变量的获取方法和设置方法。若属性为“strong关系”(strong relationship),则设置的属性值会保留。比方说,有个名叫foo的属性由名为_foo的实例变量所实现,那么,该属性的设置方法会是这样:
-(void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}
此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要。假如还未保留新值就先把旧值释放了,而两个值又指向同一个对象,那么,先执行release操作就可能导致系统将此对象永久回收。而后续的retain操作则无法令这个已经彻底回收的对象复生,于是实例变量就成了悬挂指针。
自动释放池
在Objective-C的引用计数架构中,自动释放池是一项重要特性。调用release会立刻递减对象的保留计数(而且还可能令系统回收此对象),然而有时候可以不调用它,改为调用autorelease,此方法会在稍后递减计数,通常是在下一次“事件循环”(event loop)时递减,不过也可能执行得更早些。
此特性很有用,尤其是在方法中返回对象时更应该用它。在这种情况下,我们并总是想令方法调用者手工保留其值。比方说,有下面这个方法:
-(NSString *)stringValue {
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return str;
}
此时返回的str对象其保留计数比期望值要多1,因为调用者alloc会令保留计数加1,而又没有与之对应的释放操作。保留计数多1,就意味着调用者要负责处理多出来的这一次保留操作。必须设法将其抵消。这并不是说保留计数本身就一定是1,它可能大于1,不过那取决于“initWithFormat:”方法内的实现细节。你要考虑的是如何将多出来的这一次保留操作抵消掉。但是,不能在方法呢你释放str,否则还没等方法返回,系统就把该对象回收了。这里应该用autorelease,它会在稍后释放对象,从而给调用者留下了足够长的时间,使其可以在需要时先保留返回值。换句话说,此方法可以保证对象在跨越“方法调用边界”(method call boundary)后一定存活。实际上,释放操作会在清空最外层的自动释放池时执行,除非你有自己的自动释放池,否则这个时机指的就是当前线程的下一次事件循环。改写stringValue方法,使用autorelease来释放对象:
-(NSString *)stringValue {
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return [str autorelease];
}
修改之后,stringValue方法把NSString对象返回给调用者,此对象必然存活。所以我们能够如此使用它:
NSString *str = [self stringValue];
NSLog(@"The string is: %@", str);
由于返回的str对象将于稍后自动释放,所以多出来的那一次保留操作时自然就会抵消,无须再执行内存管理操作。因为自动释放池中的释放操作要等到下一次事件循环时才会执行,所以NSLog语句在使用str对象前不需要手工执行保留操作。但是,假如要持有此对象的话(比如将其设置给实例变量),那就需要保留,并于稍后释放:
_instanceVariable = [[self stringValue] retain];
//... [_instaceVariable release];
由此可见,autorelease能延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间。
保留环
使用引用计数机制时,经常要注意的一个问题就是“保留环”(retain cycle),也就是呈环状相互引用的多个对象。这将导致内存泄露,因为循环中的对象其保留计数不会降为0。对于循环中的每个对象来说,至少还有另外一个对象引用着它。
如上图,在这个循环里,所以对象的保留计数都是1。在垃圾收集环境中,通常将这种情况认定为“孤岛”(island of isolation)。此时,垃圾收集器会把三个对象全部回收。而在Objective-C的引用计数架构中,则享受不到这一便利。通常采用“弱引用”(weak reference)来解决此问题,或是从外界命令循环中的某个对象不再保留另外一个对象。这两种办法都能打破保留环,从而避免内存泄露。
小结
引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁。
在对象生命周期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。
参考书籍:《Effective Objective-C 2.0》
Objective-C中的引用计数的更多相关文章
- swift内存管理中的引用计数
在swift中,每一个对象都有生命周期,当生命周期结束会调用deinit()函数进行释放内存空间. 观察这一段代码: class Person{ var name: String var pet: P ...
- juce中的引用计数
这个类提供了最基本的引用计数管理,界面库中,经常都需要消息发送,而带来的后果就是不知道消息中包含的对象是否还存在,如果不能很好管理的话就容易出现访问销毁了的对象这样的情况,所以,juce的界面无素也基 ...
- (20)Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
引用计数 引用计数是c/c++项目中一种古老的内存管理方式.当我8年前在研究一款名叫TCPMP的开源项目的时候,引用计数就已经有了. iOS SDK把这项计数封装到了NSAutoreleasePool ...
- Python中的引用计数法
目录 引用计数法 增量操作 计数器溢出的问题 减量操作 终结器 插入计数处理 引用计数法 增量操作 如果对象的引用数量增加,就在该对象的计数器上进行增量操作.在实际中它是由宏Py_INCREF() 执 ...
- java中垃圾回收机制中的引用计数法和可达性分析法(最详细)
首先,我这是抄写过来的,写得真的很好很好,是我看过关于GC方面讲解最清楚明白的一篇.原文地址是:https://www.zhihu.com/question/21539353
- cocos2d-x-3.3rc2-003 cocos中的引用计数和自己主动释放池
点击打开链接
- 深入理解 PHP7 中全新的 zval 容器和引用计数机制
深入理解 PHP7 中全新的 zval 容器和引用计数机制 最近在查阅 PHP7 垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我一度非常迷惑. 仔细一想不难发现问题所 ...
- c语言模拟实现oc引用计数
#include<stdio.h> #include<stdlib.h> //在c中引入 引用计数机制 // 要解决的问题: 1,指向某块动态内存的指针有几个? // ...
- ARC————自动引用计数
一.内存管理/引用计数 1.引用计数式内存管理的方式(下面四种) 对象操作 OC方法 生成并持有对象 alloc/new/copy/mutableCopyd等方法 持有对象 retain方法 释放对象 ...
随机推荐
- Shell应用之遍历回收站RecycleBin
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
- JAVA增删改查XML文件
最近总是需要进行xml的相关操作. 不免的要进行xml的读取修改等,于是上网搜索,加上自己的小改动,整合了下xml的常用操作. 读取XML配置文件 首先我们需要通过DocumentBuilderFac ...
- 《C++ Primer Plus》第5章 循环和关系表达式 学习笔记
C++提供了3种循环: for 循环. while 循环 和 do while 循环 .如果循环测试条件为 true 或非零,则循环将重复执行一组指令: 如果测试条件为 false 或 0 , 则结束 ...
- 85、android handler的警告Handler Class Should be Static or Leaks Occur
转载:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1106/1922.html 在使用Handler更新UI的时候,我是这样写 ...
- 微信开发工具包,jar包
https://www.oschina.net/code/snippet_218887_22896 github地址:https://github.com/wuweiit/weixinapi
- org.apache.hadoop.yarn.exceptions.InvalidAuxServiceException: The auxService: mapreduce_shuffle do
在yarn-site.xml 配置文件中增加: <property> <name>yarn.nodemanager.aux-services</name> < ...
- Angular2+ 基本知识汇总
Angular是Google推出的Web前端开发框架,从12年发布起就受到了强烈的关注,他首次提出了双向绑定的概念,让人耳目一新. Angular 2特性 就在2016年9月中旬,时隔4年,Googl ...
- 160307、Java调用Oracle存储过程返回结果集
一:无返回值的存储过程调用 存储过程: CREATE OR REPLACE PROCEDURE PRO_1(PARA1 IN VARCHAR2,PARA2 IN VARCHAR2) AS BEGI ...
- JAVAWEB的Listener
学习监听器 1监听器是什么 :监听对象的变化或者监听事件的触发 2有什么作用:当被监听的对象状态改变时,触发对应的方法 3怎么用: ①声明监听器,(继承对应的监听器) ②重写监听方法,并实现自己需要的 ...
- Put queue for MemoryTransaction of capacity 10000 full, consider committing more frequently, increasing capacity or increasing thread count flume capacity 时间数
package com.test; import org.apache.http.*;import org.apache.http.entity.ContentType;import org.apac ...