今天看到了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上的总结,然后再写一个随笔来总结实际开发中类对象的处理规律。

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

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

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

  2. cocos2dx 内存管理的理解

    关于引擎内存管理的细节,网上有大量的详解,这里概括一下: cocos2d-x 的世界是基于 CCObject 类构建的,所以内存管理的本质就是管理一个个 CCObject. //CCObject 内部 ...

  3. 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 ={}; ...

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

    数组是大多数编程语言都提供的一种复合结构,如果程序需要多个类型相同的变量时,就可以考虑定义一个数组,java语言的数组变量时引用类型的变量,因此具有java引用变量的特性.在使用数组之前必须对数组对象 ...

  5. Cocos2d-x 3.2 的内存管理详解

    目标读者:了解 Cocos2d-x 中的节点以及节点树,了解引用计数,了解游戏主循环等概念. 本文首先介绍 Cocos2d-x 3.2 中内存管理的作用,以及各个作用的应用.借由通俗易懂的解释来了解内 ...

  6. Linux堆内存管理深入分析(上)

    Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全   0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...

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

    第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处 ...

  8. Objective-C之集合对象的内存管理

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  9. Linux堆内存管理深入分析

    (上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...

随机推荐

  1. spark streaming 对接kafka记录

    spark streaming 对接kafka 有两种方式: 参考: http://group.jobbole.com/15559/ http://blog.csdn.net/kwu_ganymede ...

  2. lua 代码风格

    参考  http://www.kancloud.cn/kancloud/lua_style_guide/66327 1.命名 1.命名法:小驼峰命名法,大驼峰命名法(Pascal命名法),小下划线命名 ...

  3. hdu4044 GeoDefense

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4044 题意:一个树上的塔防游戏.给你n个结点的树,你要在树结点上建塔防御,在第 i 个结点上有 ki ...

  4. @RequestMapping(value = "{adminPath}")

  5. JVM内存区域介绍

    学习JVM第一个要了解的就是JVM的内存区域. Java虚拟机在运行时会从操作系统内存中划分一部分出来作为JVM内存,而JVM内存又划分为以下几个区域: 大体上可以分为两种: 线程共享数据区 该类型的 ...

  6. PCIE学习

    PCIe在传输中用8b/10b编码,所以单PCEe2.0的有效带度是4Gb/s x2模式将用于内部接口而非插槽模式 PCIe卡能使用在至少与之传输通道相当的插槽上(例如x1接口的卡也能工作在x4或x1 ...

  7. 产生某个区间的随机整数 int #Java

    int max = Integer.MAX_VALUE; int min = 1; Random random = new Random(); int s = random.nextInt(max)% ...

  8. codeforces 742E (二分图着色)

    E. Arpa's overnight party and Mehrdad's silent entering Note that girls in Arpa's land are really at ...

  9. 原生js arguments 用法

    http://note.youdao.com/noteshare?id=fcd201e872e1ede16ce0057a1909f613

  10. JavaScript基础知识整理(1)

    粗略理解,努力入门中 1.在html中引入外部脚本:  <script src="filename.js"></script> 2.注释:  多于一行的长注 ...