现在我们使用Objective-C编写iOS和Mac OS App的时候都是使用的是ARC来进行内存管理的。用一句话来总结ARC的功能的话,就是ARC使用编译器来代替程序员做内存管理的工作。虽然编译器帮我们做了内存管理工作,但是我们应该弄清楚编译器帮我们做了哪些工作,哪些工作是编译器无法做的,还是需要我们程序员来完成的。
OC中对象的内存管理是基于引用计数的内存管理:(Reference Counted Memory Management)
我们可以想象一下我们在系统中使用对象的情形,在需要的时候向系统去申请内存,不需要的时候释放对象内存,还给系统。如果长期霸占着不需要的内存而不释放,那就是刷流氓,会造成系统的内存泄露。内存泄露的多了,系统有可能会崩溃。
内存管理 == 引用计数,如果我们明白了引用计数的原理和使用的规则,OC的内存管理就变的简单了。
对象使用引用计数方法使得内存管理变的很简单。
我们需要弄清楚两个问题:1. 引用计数的对象是什么?2. 引用计数是如何变化的?
引用计数的对象是什么:这个问题很简单,在这里我们讨论的引用计数的对象就是Objective-C对象。
引用计数是如何变化的:要想回答这个问题,我们需要先回答另一个问题,就是有哪些操作会影响引用计数的变化?
影响引用计数的变化的操作,大致有以下4种:
- 创建对象从而获得对象的所有权(have ownership),引用计数+1, 从0变成1
- 持有对象从而取得对象的所有权(take ownership),引用计数+1
- 释放对象从而放弃对象的所有权(relinguish ownership),引用计数-1;
- 如果一个对象的引用计数-1后为0,意味着这个对象不存在所有权关系,没有被其它对象需要的可能,也就没有存在的意义,系统会销毁(destroy)对象回收对象分配的内存。这个销毁操作是系统自动进行的。
我们可以发现一个共同点,上面的这些操作都是针对对象的所有权(ownership)进行的,对象的所有权可以看作是操作对象的凭证或者授权,没有对象的所有权是不能对该对象进行操作的。
所以要想操作一个对象,首先必要要获得对象的所有权,获得所有权的方法有两种:
- 如果这个对象已经存在了,我们只需要声明持有这个对象来获得对象的所有权,同时对象的引用计数会+1
- 如果对象不存在,我们需要新创建一个对象,同时自然会获得这个新建对象的所有权,新建对象的引用计数从0变成1。
对于一个我们没有所有权的对象,是不能进行操作的,如果不小心操作了一个没有所有权的对象,程序有可能会崩溃。
OC中有相应的方法来进行对象所有权操作的方法:
对象所有权操作描述 OC方法
新建对象获得对象的所有权 alloc/new/copy/mutableCopy
取得已有对象的所有权 retain
放弃对象的所有权 release
销毁对象(系统自动调用) dealloc
内存管理需要遵循的几个规则:
- 能够获得任何新创建对象的所有权:使用alloc/new/copy/mutableCopy开头的方法来新建对象,并取得对象的所有权。我用四个字简单总结就是你建你有。
- 能够取得其它对象(不是自己创建的)的所有权:可以使用retain方法取得对象的所有权来操作对象。对于不是以alloc/new/copy/mutableCopy开头的方法返回得到的对象,虽然对象存在,但是我们没有该对象的所有权,不能对它进行操作。只有通过retain方法取得对象的所有权后,才能进行相应的操作。简单总结就是他建你有。
- 及时放弃不需要的对象的所有权:当一个对象不需要时,不管是通过新建对象还是使用retain方法获得的对象所有权,都必须要调用release方法来放弃对该对象的所有权。简单来说就是没用快扔。
- 不能放弃你没有所有权的对象:对于没有所有权的对象,不能调用release方法,一旦调用了release方法,程序就有可能出错奔溃。简单来说就是没有别动。
如果想要进一步弄清楚OC对象的内存管理规则,必须要清楚几个问题和细节:
- 引用一个对象和持有一个对象的区别?或者说获得对象和获得对象所有权的区别?
面向对象的知识告诉我们,一个对象的引用其实就是对象在内存中的地址,通过引用我们可以找到我们需要的对象的位置。OC中的内存管理告诉我们,持有一个对象是指我们不仅能够知道对象在内存中的位置,而且我们还拥有对象的所有权,有了获得了对象的所有权后,我们能够操作这个对象。
举个不太恰当的例子:比如说你暗恋一个小姐姐,同时你也知道了小姐姐的住址,但是小姐姐并没有同意和你在一起,此时你只是有了小姐姐的地址,然而并不能对小姐姐做什么;如果你不仅知道了小姐姐的住址,小姐姐也喜欢你同意和你在一起了,此时你获得了小姐姐的所有权(不太恰当的说法),然后你就能对小姐姐进行一番操作了。这就是引用一个对象和持有一个对象的区别。
下面使用一些代码例子来说明内存管理和引用计数:
Example 1: 创建对象并获得对象所有权
// 使用alloc方法,并获得对象所有权
id obj1 = [[NSObject alloc] init];
// 使用new方法,并获得对象所有权
id obj2 = [NSObject new];
Example 2: 不是自己创建的对象,获得对象所有权
// 获得一个不是自己创建的对象
id obj1 = [NSMutableArray array];
// 获得对象的引用,但是没有获得对象的所有权
[obj1 retain];
//获得了对象的所有权
Example 3: 不需要的对象,快快放弃对象的所有权
//创建对象并获得对象所有权
id obj = [[NSObject alloc] init];
//获得对象的所有权,可以操作对象
...
[obj release];
// 放弃对象的所有权,但是变量obj还是指向对象,即对象的引用。但是不能访问对象和操作对象。
Example 4: 方法返回对象时的所有权关系
//假设allocMyObject和myObject方法存在于MyClass中
- (id)allocMyObject {
//新建对象并获得所有权
id obj = [[NSObject alloc] init];
// 方法取得对象的所有权
return obj;
//返回对象,并转把对象的所有权转移到方法调用者
}
- (id)myObject {
//新建对象并获得所有权
id obj = [[NSObject alloc] init];
[obj autorelease];
//方法放弃对象的所有权
return obj;
//返回对象,方法调用者无法取得对象的所有权
}
//myObj是MyClass的一个实例对象
//通过alloc开头的方法创建的对象,获得对象的所有权
id obj1 = [myObj allocMyObject];
//获得对象的引用,但是没有取得对象的所有权
id obj2 = [myObj myObject];
Example 5: 不能操作没有所有权的对象
// 新建一个对象并取得对象的所有权
id obj1 = [[NSObject alloc] init];
//具有对象的所有权
[obj1 release];
//放弃对象的所有权,此时不能操作对象
[obj1 release];
// 操作了一个没有所有权的对象,系统可能崩溃
// 获得一个对象的引用,但没有取得对象的所有权
id obj2 = [myObj myObject];
// 此时没有对象的所有权
[obj2 release];
// 操作了一个没有所有权的对象,系统可能崩溃
总结
可以用一个狗和绳圈的比喻来描述对象引用计数的过程变化:狗脖子要有绳圈才能上街。当需要带狗上街时,你就应该给它套上一个绳圈,表明你取得了这个狗的所有权。当别人看上了你的狗,他也可以给这个狗加上一个绳圈,表明也取得了这个狗的所有权。只要狗的脖子上面还有绳圈,说明这个狗是有人所有的,或者说是有需求的,这时候这个狗是跑不掉的。当别人不需要这个狗时,就会解开他拥有的绳圈,表明放弃了对这个狗的所有权。当所有人都解开了绳圈,放弃对这个狗的所有权时。狗的脖子上是没有绳圈的,这时狗子一看,没有绳圈套着了,赶紧溜吧。这里的就相当于一个对象,脖子上的绳圈就是引用计数。
引用计数的需要遵循的规则:
- 你建你有
- 他建你有
- 没用快扔
- 没有别动
我们还需要分清楚对象的引用和对象的所有权的区别,想想小姐姐的故事。
如果觉得有用可以去GitHub给个。
参考:Pro Multithreading and Memory Management for iOS and OS X: With ARC, Grand Central Dispatch and Blocks.