文档章节

Objective-C中的引用计数

Chars-D
 Chars-D
发布于 2016/03/01 21:22
字数 2742
阅读 32
收藏 3
点赞 1
评论 0

 导言

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:1337];
[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:1337];
[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:1337];
[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时,对象就被销毁。

在对象生命周期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。

 

© 著作权归作者所有

共有 人打赏支持
Chars-D

Chars-D

粉丝 18
博文 69
码字总数 118270
作品 15
广州
程序员
《从零开始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理

原创文章,欢迎转载。转载请注明:关东升的博客 在Swift原生数据类型、Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,虽然是大部分是可以零开销桥接,零开销并不意味...

智捷课堂 ⋅ 2016/01/18 ⋅ 0

《Objective-C高级编程》内存管理

图片来自网络.jpg 前言 ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数...

_誌念 ⋅ 2017/12/26 ⋅ 0

理解 iOS 的内存管理

引言: ARC的出生及成长背景 苹果在 2011 年的时候,在 WWDC 大会上提出了自动的引用计数(ARC)。ARC 背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码,...

满脸胡茬的小码农 ⋅ 2017/11/17 ⋅ 0

Swift专题讲解十六——ARC在Swift中的应用

Swift专题讲解十六——ARC在Swift中的应用 一、引言 ARC(自动引用计数)是Objective-C和Swift中用于解决内存管理问题的方案。在学习Objective-C编程时经常会学习到一个关于ARC的例子:在一个...

珲少 ⋅ 2016/05/20 ⋅ 1

探寻Objective-C引用计数本质

本文涉及到的CPU架构为arm64,其它架构大同小异。 源码来自苹果开源-runtime。 Objective-C中采用引用计数机制来管理内存,在MRC时代,需要我们手动和,在苹果引入ARC后大部分时间我们不用再...

莫云溪 ⋅ 05/26 ⋅ 0

objective-c 关键字和概念

objective-c 关键字和概念 - ithero_zhou的专栏 - 博客频道 - CSDN.NET 关键字 @ 看到这个关键字,我们就应该想到,这是Object-C对C语言的扩展,例如@interface XXX。 @interface 声明类 @i...

绿月 ⋅ 2014/03/21 ⋅ 0

objective-c内存管理基础

对于我们.net开发人员来说,.net为我们提供了自动内存管理的机制,我们不需去关心内存的管理。但是iphone开发中却是不能的。这篇文章将简述一下objective-c的内存管理机制和方法和一些特性。...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

理解 ARC 下的循环引用

ARC 下的循环引用类似于日本的 B 级恐怖片。当你刚成为苹果开发者,你或许不会关心他们的存在。直到某天你的一个 app 因内存泄露而闪退,你才突然意识到他们的存在,并且发现循环引用像幽灵一...

那条鱼 ⋅ 2016/03/30 ⋅ 0

objective-c 面试题

objc的内存管理: bjec内存管理采用引用计数的方式。当你使用new,alloc或copy创建对象时,对象的引用计数由0自增到1,调用release方法之后对象的引用计数减一,当且仅当对象的引用计数为0的时...

青云_K ⋅ 2013/02/04 ⋅ 0

JavaScriptCore框架在iOS7中的对象交互和管理

之前一篇的文章中已经简单入门了iOS7中新加的JavaScriptCore框架的基本用法,十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,...

北方人在上海 ⋅ 2016/03/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

来自一个优秀Java工程师的简历

写在前面: 鉴于前几天的一份前端简历,虽然带着很多不看好的声音,但却帮助了很多正在求职路上的人,不管评论怎么说,我还是决定要贴出一份后端的简历。 XXX ID:357912485 目前正在找工作 ...

颖伙虫 ⋅ 24分钟前 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

Linux系统日志

linux 系统日志 /var/log/messages /etc/logrotate.conf 日志切割配置文件 https://my.oschina.net/u/2000675/blog/908189 logrotate 使用详解 dmesg 命令 /var/log/dmesg 日志 last命令,调......

Linux学习笔记 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部