Object-C内存管理的理解总结

今天看到了OC的内存管理这块,觉得很亲切。

自己的习惯是尽量自己掌控程序的空间和时间,有点强迫症的感觉。用C和C++做项目的时候,时时刻刻都在操心这new和delete的配对使用和计数,学习stl和boost的时候看到了智能指针等时候,依然不是很爱使用,还是愿意坚持自己控制new和delete;后来用C#后,一直特别注意Dispose相关的动作,尽早释放对象占有的内存空间,避免无谓的占用一直到程序退出才释放。

OC中系统对每个实例对象地址都记录一个引用次数(当然有特例,见另外一篇随笔),这就是引用计数,在很多面向对象语言中都被采用。

这里总结一下OC中引用计数的显式操作过程:

1. 计数置1:每个实例对象在实际显式alloc创建的时候都会做到引用计数置1。

2. 计数加1: 向对象显式发送retain消息会将引用计数加1。

3. 计数减1: 向对象显式发送release消息会将引用计数减1。

4. 计数为0: 当对象引用计数为0时候,OC系统会自动向对象发送实际释放的消息dealloc。

5. 计数不变:同类型对象的简单赋值不会引起计数改变,结果是两个对象指向相同的内存地址,也就具有相同的计数值,包括计数为0的状态。

下面这段代码很清楚的反应了上面的5个状态。

NSObject * obj = [[NSObject alloc] init]; //obj ref count = 1

NSObject * obj1 = obj; //obj1 ref count = 1

[obj retain]; //obj ref count = 2, obj1 ref count =2

[obj release]; //obj ref count = 1, obj1 ref count = 1

[obj release]; //obj ref count = 0, obj1 ref count = 0

NSObject * obj2 = obj; //obj2 ref count = 0

虽然最后一句看起来我们不会做,但是实际复杂代码中经常会出现不知情的指向了一个引用计数已经为0对象的情况。为什么会出现这种情况,大多数是由于代码中很多隐式引用计数操作引起的。

和上面的类似,我们总结一下引用计数的隐式操作过程,这个也是我们在开发构成中要谨慎细心留意的,特别是在自定义的类型的实现中。

1. 计数置1:所有通过间接调用alloc方法返回对象实例的方法都会将新产生对象的引用计数置1。包括:继承于NSObject的类型(包括Foundation提供的类型和我们自定义的类型)都提供了很多构造alloc和初始化结合的便捷方法,那么它们都会将对象的引用计数置1。比如NSArray的arrayWithObject,NSMutableString的stringWithString等等;对象的深拷贝产生的新对象也是直接或者间接的调用了alloc,那么它的计数也被置1。

2. 计数加1: 所有通过间接调用retain的方法都会将对象的引用计数加1。包括:所有Foundation定义的集合类型(Array, Dictionary, Set等)的添加新元素的方法,比如addObjectAtIndex等都会将添加进入的对象的引用计数加1,实际它们是给该对象发送了retain消息。自己定义的集合类型应该遵守这个约定。

3. 计数减1: 所有通过间接调用release的方法都会将对象的引用计数减1。包括:所有Foundation定义的集合类型的删除对象元素的方法,比如removeObjectAtIndex等都会把对象的引用计数减去1,集合自己release的时候也会给每个元素对象发送release消息以使得元素对象引用减1;此外自动释放池也会在drain的时候给注册到它内部的每个对象发送release消息,以使得对象的引用计数减1。自己定义的集合类型也应该遵守这个约定。

下面描述一下无效对象引用的表现:

计数为0时候OC系统的动作是自动触发的,因为计数为0意味着该对象没有人引用了,那么就可以触发dealloc了,dealloc中必须完成该对象占有资源的释放,然后系统会回收为该对象分配的内存,此时,对象的引用都变成无效的了。需要强调的是,当对象的引用无效后,在不知情的情况下依然通过该引用操作对象的结果是不确定的,因为系统回收了为该对象分配的内存后,不代表该内存被立即被其他数据占用或破坏,所以在某些情况下,会出现对象看起来还能正常工作的现象。好的习惯是,在某个地方release了对象后,将此处引用重新赋值为nil,这样避免继续操作这个对象引用来继续做事。

自动释放池也是个不错的概念,其实可以把方法局部变量看作在一个自动释放池中,这个池的范围是一个方法,在方法出口一定会释放这些局部变量本身占有的内存空间。OC系统的自动释放池扩展了这种方法局部变量的概念,(其他语言中,比如最新的C++和C#标准可以用花括号对{}来表达一个局部变量的生命范围,C#的using调用等,以此达到这种自动释放的效果)。

当然自动释放池和这些也是有所不同的,因为对象如果需要由自动释放池通知release,它必须首先把自己注册到当前的自动释放池中。

首先看注册对象自己到自动释放池的过程:

1. 显式标记自动释放:对对象发送autorelease消息,就会把对象注册到当前自动释放池里。

2. 隐式标记自动释放:在初始化函数实现中调用autorelease消息,这样在对象构造的时候自动将自己注册到当前自动释放池中。

来看段代码:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSObject * obj = [[NSObject alloc] init]; //obj ref count = 1

NSArray * arr = [NSArray arrayWithObject: @"item"]; //arr ref count = 1

[arr retain];//arr ref count = 2

[obj retain];//obj ref count = 2

[arr retain];//arr ref count = 3

[obj retain];//obj ref count = 3

[arr release];//arr ref count = 2

[obj release];//obj ref count = 2

[pool drain];

  drain之后,这里arr和obj的引用次数是多少呢?实际的运行结果是arr ref count = 1, obj ref count = 2。

这个结果证明了,arr在构建的时候已经隐式把自己注册到了当前的自动释放池中了,而obj使用基本的alloc和init并没有包括这个隐式操作,所以pool在drain的时候,发送了release消息给arr,arr的引用计数就减为了1。如果我们在obj实例对象构建好后调用代码 [obj autorelease],那么obj在pool drain之后的引用计数也是1了。

所以,我们在自定义类型的初始化方法实现中应该也遵守这个规律,加入隐式的autorelease调用。

打完收工,娃睡着了,今个还可以做几个练习巩固一下这些EBOOK上的总结,然后再写一个随笔来总结实际开发中类对象的处理规律。

时间: 2024-12-09 13:27:43

Object-C内存管理的理解总结的相关文章

cocos2dx 使用过程中内存管理的理解

关于引擎内存管理的细节,网上有大量的详解,这里概括一下: cocos2d-x 的世界是基于 CCObject 类构建的,所以内存管理的本质就是管理一个个 CCObject. //CCObject 内部维护着一个引用计数,引用计数为 0 就自动释放 unsigned int m_uReference; //管理内存的实质就是管理这些 "引用计数" 了,使用 retain 和 release 方法对引用计数进行操作 void release(void);//引用计数:--m_uRefere

cocos2dx 内存管理的理解

关于引擎内存管理的细节,网上有大量的详解,这里概括一下: cocos2d-x 的世界是基于 CCObject 类构建的,所以内存管理的本质就是管理一个个 CCObject. //CCObject 内部维护着一个引用计数,引用计数为 0 就自动释放 unsigned int m_uReference; //管理内存的实质就是管理这些 “引用计数” 了,使用 retain 和 release 方法对引用计数进行操作 void release(void);//引用计数:--m_uReference v

Python的内存管理 小理解

请看下面的一段代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 origin = {'a':100,'b':[1,2,34,5]} obj_copy ={}; print origin; obj_copy['key1']= origin; obj_copy['key2']= origin; print(obj_copy) print('我们试图改变obj_copy中某个Key值的内容') obj_copy['key1']['a'] = 10000 pri

[java小笔记] 关于数组内存管理的理解

数组是大多数编程语言都提供的一种复合结构,如果程序需要多个类型相同的变量时,就可以考虑定义一个数组,java语言的数组变量时引用类型的变量,因此具有java引用变量的特性.在使用数组之前必须对数组对象进行初始化,当所有的数组元素都被分配了合适的内存空间,并指定了初始值是,数组初始化完成. 数组初始化分为 1.静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组的长度. 语法形式: int[] y = {1,2,3,4,5}; 2.动态初始化:初始化时程序员只指定数组的长度,由

浅谈自己对cocos2dx的内存管理的理解

拿个自己写Layer的例子 1 auto genMenuWnd = GeneralMenuWnd::create(); 2 CC_BREAK_IF(!genMenuWnd); 3 addChild(genMenuWnd, 100); 该引用计数变化流程 GeneralMenuWnd在create()的时候,其_referenceCount 为1,并在create()的时候autorelease(); addChild()的时候,其_referenceCount变为2: 当该帧执行完后,_refe

Linux内存管理 - PAGE_OFFSET理解

PAGE_OFFSET 代表的是内核空间和用户空间对虚拟地址空间的划分,对不同的体系结构不同.比如在32位系统中3G-4G 属于内核使用的内存空间,所以 PAGE_OFFSET = 0xC0000000.在X86-64架构下是ffff880000000000.可以看到内核程序可以可以访问从PAGE_OFFSET 之后的内存,访问所有的信息(注意页的写保护). 参考:https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt Virtual

OC内存管理详解

前言 由于移动设备的内存有限,所以我们需要对内存进行严格的管理,以避免内存泄露造成资源浪费.在OC中,只有对象才属于内存管理范围,例如int.struce等基本数据类型不存在内存管理的概念.在iOS开发中,对内存的管理实际上就是对引用计数器的管理. OC内存管理的三种方式 自动垃圾收集(Automatic Garbage Collection): 手动引用计数器(Manual Reference Counting)和自动释放池: 自动引用计数器(Automatic Reference Count

Linux内存管理 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-4491368.html Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法.力求从外到内.水到渠成地引导网友分析Linux的内存管理与使用.在本章最后,我们给出一个内存映射的实例,帮助网友们理解内核内存管理与用户内存管理之间的关系,希望大家最终能驾驭Linux内存管理. 前言 内存管理一向是所有操作系统书

effective OC2.0 52阅读笔记(五 内存管理)

第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位,来代替直接调用autorelease和retain.这是ARC所带来的好处.待编译器与运行期组件日臻成熟,还会出现其他的优化技术.CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease. 31 在dealloc方法中只释放引用