内存管理实践

尽管基本的概念在内存管理策略文章中简单得阐述了,但是还有一些实用的步骤让你更容易管理内存;有助于确保你的程序最大限度地减少资源需求的同时,保持可靠和强大。

使用“访问器方法”让内存管理更简单

假如,你的程序有一个对象类型的属性,你必须保证:当你使用的时候,任何的已经赋值了的对象不会被销毁。被赋新值的时候,开发者必须获得对象的所有权,并放弃正在使用对象的所有权。
有时候,这些听起来很老套和繁琐,如果开发者统一使用访问器方法,内存管理有问题的机会大大减少。如果开发者在代码中总是使用retain和release管理实例变量,几乎肯定会做错事,换句话说:访问器是必须的。
来看一个Counter对象

  1. @interface Counter : NSObject
  2. @property (nonatomic, retain) NSNumber *count;
  3. @end;

该属性声明了两个访问器。典型的做法,开发者告诉编译器合成(synthesize)访问器方法。了解访问器是如何实现的对开发者是有好处的。
在get访问器中,开发者只需要返回变量即可,不需要retain和release

  1. - (NSNumber *)count {
  2. return _count;
  3. }

在set访问器方法里,如果每一位开发者都能按照相同的规则,对新的count进行负责,开发者必须通过retain来获得对象的所有权。开发者也需要通
过release来放弃旧对象的所有权。(在Objective-c中给对象发送nil消息是允许的,即是_count没有被设置,仍然是安全的。)为了
防止两个对象是相同的,开发者需要先调用[newCount retain](开发者可不希望意外的把对象给销毁)

  1. - (void)setCount:(NSNumber *)newCount {
  2. [newCount retain];// retain new object first;
  3. [_count release];
  4. // Make the new assignment.
  5. _count = newCount;
  6. }

【使用访问器方法设置属性值】
假设你想实现一个重置counter的方法。你有多个选择。
第一种方式:用alloc创建NSNumber实例,然后可以用release释放。

  1. - (void)reset {
  2. NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
  3. [self setCount:zero];
  4. [zero release];
  5. }

第二种方法:用便捷构造方法创建NSNumber对象。因此,不用调用retain和release等。

  1. - (void)reset {
  2. NSNumber *zero = [NSNumber numberWithInteger:0];
  3. [self setCount:zero];
  4. }

注意:以上两种方法都使用访问器方法。
下面将几乎可以肯定,正常的情况下,因为它可能避开访问器方法,这样很诱人。这样做几乎肯定会导致一个错误在某些时候。(比如,当开发者忘记retain或者release,或者将来内存管理机制有变化)

  1. - (void)reset {
  2. NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
  3. [_count release];
  4. _count = zero;
  5. }

注意:如果开发者使用key-value observing,上面这种方法也不属于KVO范畴

不要在初始化和dealloc函数中使用访问器方法

唯一不让使用访问器的地方就是initializer和dealloc。为了将counter初始化为零,开发者可以这样实现:

  1. - init {
  2. self = [super init];
  3. if (self) {
  4. _count = [[NSNumber alloc] initWithInteger:0];
  5. }
  6. return self;
  7. }

为了初始化为非零数据,可以这么实现initWithCount方法:

  1. - initWithCount:(NSNumber *)startingCount {
  2. self = [super init];
  3. if (self) {
  4. _count = [startingCount copy];
  5. }
  6. return self;
  7. }

既然Counter类有一个类属性,开发者还需要实现dealloc,dealloc通过发送release,将放弃左右对象的所有权,最终dealloc还会调用父类的dealloc函数。

  1. - (void)dealloc {
  2. [_count release];
  3. [super dealloc];
  4. }

使用弱引用(Weak References)来避免循环retain

对一个对象retain操作是强引用(strong reference)。所有强引用都被release之后对象才被销毁。如果,两个对象有彼此的强引用,就出现众所周知的问题——循环retain。
(包括:直接,或通过其他对象强引用链的情况)
对象的关系如图所示,有一个潜在的循环retain。Document对象每一页都有一个Page对象。每个Page对象有一个paragraphs属性,表明该Page在那一Document中。如果Document中的Page是强引用,Page类
中的paragraphs属性也是强引用,那个对象都不会被销毁。Document的引用值直到Page对象被释放才变为0,而Page对象直到Document释放才被释放。

    解决循环引用的的方法是使用弱引用。弱引用是一种非占有所有权的关系,不对源对象retain,只是引用(reference)。

然后,为了保持对象图的完整性,强引用还是必要的(如果只有弱引用,pages和paragraphs将没有任何的所有者,也就不能被释放)Cocoa形成了一个惯例:父对象应该强引用子对象,子变量应该弱引用父对象。

所以,在图1中,Document对象强引用page对象,page对象弱引用Document对象。

Cocoa中的弱引用例子包含了(但不限于)table data sources, outline view items,
notification observers, and miscellaneous targets and delegates.

开发者给弱引用对象发送消息应小心一些。如果给一个已经销毁的对象发消息,程序将crash。当对象可用的时候,开发者需具备良好的定义条件(You
must have well-defined conditions for when the object is
valid.)。大多数情况:弱引用对象知道其他对象弱引用了自己,当自己被销毁的时候,有责任通知其他对象。比如,当开发者用notification
center注册一个对象,notification center存储一个弱引用对象,并发送post消息给对象。当对象已经被销毁了
开发者需从notification center中注销对象,防止notification
center发送消息给不存在的对象。同样,当一个delegate对象被销毁后,开发者嘘移除delegate通过发送一个参数为nil的
setDelegate消息而这些消息通常从对象的dealloc中发送。

避免销毁正在使用的对象

Cocoa's所有权策略指定接受的对象应该,保证在函数调用范围可用;还可以返回接受的对象,而不用担心被release掉。保证从程序中的gerrer方法中返回实例变量或者计算后的值是没问题的。
问题是,当需要的时候对象仍然有效。
偶尔有些例外,主要是下面两种情况:
1.从基础集合类中移除对象

  1. heisenObject = [array objectAtIndex:n];
  2. [array removeObjectAtIndex:n];
  3. // heisenObject could now be invalid.

当对象从基本集合类移除,集合类发送一个release(而不是autorelease)消息。如果集合类是对象的唯一拥有者,被移除的对象(例子中heisenObject)就被立即销毁。
2.父对象被销毁

  1. id parent = <#create a parent object#>;
  2. // ...
  3. heisenObject = [parent child] ;
  4. [parent release]; // Or, for example: self.parent = nil;
  5. // heisenObject could now be invalid.

某些情况,开发者从其他对象获得一个对象,直接或见解释放父对象。release父对象导致被销毁,父对象又是子对象的唯一拥有者,子对象将同时被销毁。
防止这些情况,当开发者收到heisenObject时retain,使用完release掉,比如:

  1. heisenObject = [[array objectAtIndex:n] retain];
  2. [array removeObjectAtIndex:n];
  3. // Use heisenObject...
  4. [heisenObject release];

不要对稀缺资源进行dealloc

不要在dealloc函数中管理file descriptor、network
connections、buffer和caches这些资源。通常,开发者不应设计带有dealloc这样的类。dealloc可能延迟调用,要么就成
为程序的一个bug或者造成程序崩溃。

相反,如果你有一个稀缺资源的类,你应该这样设计应用程序,例如,你知道当你不再需要的资源的时候,然后可以告诉实例clean up。通常,你会再释放该实例,紧接着调用dealloc,你不会受到额外的问题。

如果您尝试在dealloc中背驮式得资源管理,可能会出现问题。
1.顺序依赖被拆散
虽然开发者可能希望得到一个特定顺序,被拆散的对象图本质上是无序的。如果对象是被autorelease的,被拆散的顺序可能还有变化,也可能导致意想不到的结果。
2.非回收式的稀缺资源
内存泄露是可以修复bug,内存泄露的伤害不是立即致命的。如果稀缺资源在不能释放的时候,被你释放了,你可能会碰到更严重的问题。
如果你的应用程序使用文件描述符(file descriptor),可能导致不能写数据。
3.在错误的线程中执行cleanup逻辑
如果一个对象被开发者设置为是autorelease的,它会被任意一个“它正好存在于的”线程的自动释放池给释放掉。这是很容易的致命错误:该资源应该在一个线程中使用和释放。
(If an object is autoreleased at an unexpected time, it will be
deallocated on whatever thread’s autorelease pool block it happens to be
in. This can easily be fatal for resources that should only be touched
from one thread)

集合拥有它所包含的对象

当你添加一个对象到集合(如,array,dictionary和set),集合获得对象的所有权。对象被移除的时候或者集合本身release的时候,放弃对象的所有权。
如果开发者想创建一个带有粒度的array,可以这么搞:

  1. NSMutableArray *array = <#Get a mutable array#>;
  2. NSUInteger i;
  3. // ...
  4. for (i = 0; i < 10; i++) {
  5. NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
  6. [array addObject:convenienceNumber];
  7. }

这种情况,开发者没有调用alloc,所以无需掉用release。也没有必要retain新的convenienceNumber。

  1. NSMutableArray *array = <#Get a mutable array#>;
  2. NSUInteger i;
  3. // ...
  4. for (i = 0; i < 10; i++) {
  5. NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
  6. [array addObject:allocedNumber];
  7. [allocedNumber release];
  8. }

这个情况,开发者需要向allocedNumber发送一个release消息在for的作用域之内,来匹配alloc。既然array的addObject方法 retain了allocedNumber,allocedNumber就会被array管理删除。
要像理解这些,把自己当做设计集合类的开发者们。你想保证:即使不在你照看下,集合中的变量能仍然好用,所以,当对象传入的时候,你retain了一次。当对象移除集合类,你需要发送release消息。

对象所有权策略是基于引用计数实现的

对象所有权的策略是通过引用计数——通常叫做retain count实现的。每一个对象有一个retaincount变量。
● 创建对象后,它的retaincount是1
● retain之后,retain count +1
● release之后 retain count -1 
● autorelease之后,在自动释放池最后-1
● 对象的retain count减少到0的时候,对象被销毁。

【重要】
不要显式调用对象的retainCount,结果往往具有误导性,作为开发者可能不了解框架式如何对对象retain的。
在调试内存管理中,你应该只关注确保你的代码遵循所有权规则。

iOS 内存管理实践的更多相关文章

  1. iOS内存管理策略和实践

    转:http://www.cocoachina.com/applenews/devnews/2013/1126/7418.html 内存管理策略(memory Management Policy) N ...

  2. 【Bugly干货分享】iOS内存管理:从MRC到ARC实践

    Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 对于iOS程序员来说,内存管理是入门的 ...

  3. iOS 内存管理:从 MRC 到 ARC 实践

    对于 iOS 程序员来说,内存管理是入门的必修课.引用计数.自动释放等概念,都是与 C 语言完全不同的. iOS 内存管理的核心是引用计数. 接触 MRC 时遇到最头疼的问题就是:为什么那么多 rel ...

  4. iOS内存管理

    iOS内存管理的方式是引用计数机制.分为MRC(人式引用计数)和ARC(自动引用计数). 为什么要学习内存管理? 内存管理方式是引用计数机制,通过控制对象的引用计数来实现操作对象的功能.一个对象的生命 ...

  5. iOS内存管理个人总结

    一.变量,本质代表一段可以操作的内存,她使用方式无非就是内存符号化+数据类型 1.保存变量有三个区域: 1>静态存储区 2>stack 3>heap 2.变量又根据声明的位置有两种称 ...

  6. IOS内存管理学习笔记

    内存管理作为iOS中非常重要的部分,每一个iOS开发者都应该深入了解iOS内存管理,最近在学习iOS中整理出了一些知识点,先从MRC开始说起. 1.当一个对象在创建之后它的引用计数器为1,当调用这个对 ...

  7. iOS内存管理编程指南

    iOS 内存管理 目录[-] 一:基本原则 二:成员变量的内存管理 三:容器对象与内存管理 四:稀缺资源的管理 五:AutoRelease 六:其他注意事项 iOS下内存管理的基本思想就是引用计数,通 ...

  8. iOS内存管理(一)

    最近有时间,正好把iOS相关的基础知识好好的梳理了一下,记录一下内存相关方面的知识. 在理解内存管理之前我觉得先对堆区和栈区有一定的了解是非常有必要的. 栈区:就是由编译器自动管理内存分配,释放过程的 ...

  9. iOS内存管理 ARC与MRC

    想驾驭一门语言,首先要掌握它的内存管理特性.iOS开发经历了MRC到ARC的过程,下面就记录一下本人对iOS内存管理方面的一些理解. 说到iOS开发,肯定离不开objective-c语言(以下简称OC ...

随机推荐

  1. MySQL丨01丨数据库基本概念

    以前记录数据可能很少也很简单,比如说老王借了老李半斤肉,这样的数据老李直接就写到墙上就行了. 后来数据多了人们就以表格的方式开始记录,写到一张A4纸上,比如学生的档案,有表头和序号等. 表头里有姓名. ...

  2. linux中的硬盘及flash操作

    磁盘操作是块设备的必备操作,需要认真掌握. 一.硬盘 1.硬盘文件 默认串口硬盘的设备文件为sda(第一块硬盘).sdb(第二块硬盘).... 默认并口硬盘的设备文件为hda(第一块硬盘).hdb(第 ...

  3. hashlib模块,hmac模块

    6.11自我总结 1.hashlib模块(文件传输中将传输内容用指定算法进行处理) hash是一种算法(Python3.版本里使用hashlib模块代替了md5模块和sha模块,主要提供 SHA1.S ...

  4. 数据结构( Pyhon 语言描述 ) — — 第1章:Python编程基础

    变量和赋值语句 在同一条赋值语句中可以引入多个变量 交换变量a 和b 的值 a,b = b,a Python换行可以使用转义字符\,下一行的缩进量相同 )\ 帮助文档 help() 控制语句 条件式语 ...

  5. pytorch 加载数据集

    pytorch初学者,想加载自己的数据,了解了一下数据类型.维度等信息,方便以后加载其他数据. 1 torchvision.transforms实现数据预处理 transforms.Totensor( ...

  6. AVL树总结

    定义:一棵AVL树或者是空树,或者是具有下列性质的二叉搜索树:它的左子树和右子树都是AVL树,且左右子树的高度之差的绝对值不超过1 AVL树失衡旋转总结: 假如以T为根的子树失衡.定义平衡因子为 H( ...

  7. 学习笔记4——WordPress插件介绍

    1.什么是WordPress插件? WordPress有三大组件:核心.主题.插件. 插件是扩展了WordPress核心功能的代码包.WordPress插件由PHP代码和其他资源(如图像,CSS和JS ...

  8. Leetcode 377.组合总和IV

    组合总和IV 给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数. 示例: nums = [1, 2, 3] target = 4 所有可能的组合为: (1, 1, 1, ...

  9. 2017年 湘潭邀请赛(湖南)or 江苏省赛

    这套题是叉姐出的,好难啊,先扫一遍好像没有会做的题了,仔细一想好像D最容易哎 Super Resolution Accepted : 112   Submit : 178 Time Limit : 1 ...

  10. 【Luogu】P2831愤怒的小鸟(手算抛物线+状压DP)

    题目链接 设f[s]表示二进制集合表示下的s集合都打掉用了多少小鸟. 预处理出lne[i][j]表示i.j确定的抛物线能打掉的小鸟集合. 于是就有f[s|lne[i][j]]=min(f[s|lne[ ...