初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存管理这一部分说的非常简单,只有三条准则:

  1. 当你使用new、alloc或copy方法创建一个对象时,该对象的保留指针为1,当不再使用该对象的时候,你应该想该对象发送一条release或autorelease消息,这样,该对象在其寿命结束时将被销毁。
  2. 当你通过其他方法获得一个对象时,假设该对象的保留计数器为1,而且已经设置为自动释放,那么你不需要执行任何操作来确保该对象被销毁。如果你打算在一段时间内拥有该对象,则需要保留它并确保它在操作完成时释放它。
  3. 如果你保留了某个对象,就需要(最终)释放或自动释放该对象。必须保持retain方法和release方法的使用次数相同。

  如果在写代码的时候遵守这些准则,可以避免内存泄露,但是如果仅靠对这些准则的“记忆”来写代码的话,恐怕自己心里都不会有底,一旦遇到问题分析问题的时候很难从根本上找到问题出现的原因,本文分享了自己在理解引用计数时的分析过程,结合相关图形,希望能让大家深刻理解对象引用计数的原理。

遇到了问题?分析然后测试

  当前对象的引用计数是多少呢?

为什么要提出这个问题,因为很多人会搞混对象的指针数量与引用数量的关系,不理解这个问题就弄不明白对象的引用计数到底为多少,当然就无法正确释放内存了。在说这个之前先简单了解一下堆内存与栈内存的概念,

  1. 栈内存:由编译器负责分配,存放局部环境中定义的基本变量的值,例如方法中的基本变量等,离开局部环境时会由编译器自动释放其内存空间。
  2. 堆内存: 一般由程序员通过new或alloc等来手动分配,使用完后也需要程序员手动释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事。

变量名实际上是一个符号地址,在对程序编译连接时由系统给每一个变量名分配一个内存地址。在程序中从变量中取值,实际上是通过变量名找到相应的内存地址,从其存储单元中读取数据。指针是一个特殊的变量,因为它存放的是一个变量的地址。如下图所示:

  上面这个内存模型相信大家都知道,指针与对象存在一个间接(指向)的关系,因此当指针指向一个对象的时候,很多人就会觉得这个指针引用到了该对象,进而就认为当指针指向一个对象的时候,该对象的引用计数就会加1,这种理解是一种感性的理解。实际上对于一个对象来说,它是不知道指向它的指针有多少个的,它的释放仅仅依靠的是引用计数,那么什么是引用计数呢?在objective-c中,大部分对象都继承于NSObject,NSObject包含一个用来保存引用数量的字段retainCount,说白了该字段就是引用计数,NSObject类的部分定义如下:
- (id)retain;
- (oneway void)release;
- (id)autorelease;
- (NSUInteger)retainCount;
- (NSString *)description;

因此,为了便于理解,我们可以把NSObject简化为如下模型:

  对象能否释放就是判断其引用次数是否为零,也就是判断该对象的retainCount字段是否等于0,而指向该对象指针数量跟该对象retainCount字段的值并没有关系,因此指针数量并不等于引用数量,当指针指向该对象的时候,仅仅是给该指针变量赋值了,并没有修改对象的retainCount值,因此,指针指向一个对象的时候,该对象的引用计数是没有改变的。

  以上面那段代码为例,我们调用Test对象的new方法的时候,会自动将retainCount的值设置为1,当我们将test1赋值给test2的时候,只是一个指针赋值,并没有修改对象的retainCount值,所以引用计数不变,依旧为1。

测试用例:

         Engine *engine1 = [Engine new];
NSLog(@"通过new消息创建对象engine1:");
//输出engine1指针地址
NSLog(@"engine1 address is %p.",engine1);
//输出engine1的retainCount
NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]); Engine *engine2 = engine1;
NSLog(@"将指针engine1复制给指针engine2:");
//输出engine2指针地址
NSLog(@"engine2 address is %p.",engine2);
//输出engine1的retainCount
NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]);
//输出engine2的retainCount
NSLog(@"engine2 retainCount is %lu",(unsigned long)[engine2 retainCount]); Engine *engine3 = [engine1 retain];
NSLog(@"通过retain消息获得对象engine3:");
//输出engine3指针地址
NSLog(@"engine3 address is %p.",engine3);
//输出engine1的retainCount
NSLog(@"engine1 retainCount is %lu",(unsigned long)[engine1 retainCount]);
//输出engine2的retainCount
NSLog(@"engine2 retainCount is %lu",(unsigned long)[engine2 retainCount]);
//输出engine3的retainCount
NSLog(@"engine3 retainCount is %lu",(unsigned long)[engine3 retainCount]); [engine2 release];
NSLog(@"给对象engine2发送消息release");
NSLog(@"engine2 address is %p.",engine2);
NSLog(@"engine2 retainCount is %lu.",(unsigned long)[engine2 retainCount]);

输出结果如下:

  从上面的输出结果可以得出以下几点结论:

  1. 和本文一开始分析得出的结果一样,通过指针赋值并不能改变对象的引用计数。
  2. 不论是通过指针赋值还是通过retain获得对象,它们都指向同一个内存地址,即:指向同一个对象
  3. 在对象的引用计数归零之前,所有指向它的指针都是可用的。通过某个指针发送release消息仅仅是让引用计数减一,该指针本身不会被销毁。

  因为这里需要输出引用计数,就没有采用ARC,所以会有一个小问题,那就是当退出局部环境的时候,即使局部指针所指向的对象已被销毁,局部指针变量的值仍然没有改变,因此需要手动赋值为nil。如果采用ARC的话,会自动回收内存并将指针赋值为nil。

总结

  不管是直接通过指针赋值还是通过retain或者copy来保留对象,都会增加指向对象的指针数量,这些指针都指向同一块内存地址,因为对象所分配的内存地址是不变的。

  指向对象的指针的多少跟引用计数没有任何关系,但是通过retain、copy或release可以改变对象的引用计数。

  仅当引用计数为0时才会释放对象占用的内存空间。  

  哎,真是“落花有意流水无情”啊,哪怕再多的指针“爱上对象”,人家这辈子却也只认引用计数。

Objective-C内存管理之引用计数的更多相关文章

  1. iOS的内存管理和引用计数规则、Block的用法以及三种形式(stack、malloc、global)

    学习内容 iOS的内存管理和引用计数规则 内存管理的思考方式 自己生成的对象自己持有 非自己生成的对象自己也能持有 自己持有的对象不需要时释放 非自己持有的对象不能释放 ARC有效时,id类型和对象类 ...

  2. Objective-C内存管理之-引用计数

    本文会继续深入学习OC内存管理,内容主要参考iOS高级编程,Objective-C基础教程,疯狂iOS讲义,是我学习内存管理的笔记 内存管理 1 内存管理的基本概念 1.1 Objective-C中的 ...

  3. Swift基础语法-内存管理, 自动引用计数

    1. 工作机制 Swift和OC一样,采用自动引用计数来管理内存 当有一个强引用指向某一个对象时,该对象的引用计数会自动+1 当该强引用消失时,引用计数会自动-1 当引用计数为0时,该对象会被销毁 2 ...

  4. Python内存管理及引用计数

    作为一门动态语言,python很重要的一个概念就是动态类型,即对象的类型和内存占用都是运行时确定的.(Why?)运行时,解释器会根据语法和右操作数来决定新对象的类型.动态类型的实现,是通过引用和对象的 ...

  5. Objective C内存管理之理解autorelease------面试题

    Objective C内存管理之理解autorelease   Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的A ...

  6. Object-C内存管理-对象引用计数的特例

    看到OC中内存管理这块,其中的引用计数部分,部分10.5上的EBOOK示例已经在10.9上不能运行正确了,比如下面的代码: NSString * str1 = @"string 1" ...

  7. Objective C 内存管理[转]

    1  配对原则 alloc – release new – release retain - release copy – release 2  new和alloc-init的区别 (1)区别只在于a ...

  8. objective C 内存管理及属性方法具体解释

    oc为每一个对象提供一个内部计数器.这个计数器跟踪对象的引用计数,当对象被创建或拷贝时.引用计数为1.每次保持对象时,调用retain接口.引用计数加1.假设不需要这个对象时调用release,引用计 ...

  9. OC语法6——内存管理之引用计数器(retain,release)

    OC内存管理: 一.引用计数器: Java有垃圾回收机制(Garbage Collection,GC).也就是说当我们创建对象后,不需要考虑回收内存的事,Java的垃圾回收机制会自动销毁该对象,回收它 ...

随机推荐

  1. 无法向会话状态服务器发出会话状态请求。请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同。如果服务器位于远程计算机上,请检查。。。

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 无法向会话状态服务器发出会话状态请求.请确保 ASP.NET State Ser ...

  2. OpenCASCADE Job - dimue

  3. Web APi之认证(Authentication)两种实现方式【二】(十三)

    前言 上一节我们详细讲解了认证及其基本信息,这一节我们通过两种不同方式来实现认证,并且分析如何合理的利用这两种方式,文中涉及到的基础知识,请参看上一篇文中,就不再叙述废话. 序言 对于所谓的认证说到底 ...

  4. 操作系统篇-hello world(免系统运行程序)

     || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希 ...

  5. Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩

    目录: 前序 效果图 简介 全部代码 前序: 接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 ...

  6. 编写高质量代码:改善Java程序的151个建议(第6章:枚举和注解___建议88~92)

    建议88:用枚举实现工厂方法模式更简洁 工厂方法模式(Factory Method Pattern)是" 创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其它子类" ...

  7. thinkphp数据的查询和截取

    public function NewsList(){ $this->assign('title','news'); $p = I('page',1); $listRows = 6; $News ...

  8. 【从零开始学BPM,Day4】业务集成

    [课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第四天课程] 1.课程概要 Step 1 软件下载:H3 BP ...

  9. 说说BPM数据表和日志表中几个状态字段的详细解释

    有个客户说需要根据这些字段的值作为判断条件做一些定制化需求,所以需要知道这些字段的名词解释,以及里面存储的值具体代表什么意思 我只好为你们整理奉上这些了! Open Work Sheet  0 Sav ...

  10. postgresql 基本语法

    postgresql数据库创建/修改/删除等写入类代码语法总结: 1,创建库 2,创建/删除表 2.1 创建表 create table myTableName 2.2 如果表不存在则创建表 crea ...