0%

自动引用计数

什么是自动引用计数?

自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。

内存管理的思考方式

  • 自己生成的对象,自己持有。
  • 非自己生成的对象,自己也能持有。
  • 不再需要自己持有的对象时释放。
  • 非自己持有的对象无法释放。

自己生成的对象,自己持有

使用以下名称开头的方法名意味着自己生成的对象自己持有:

  • alloc
  • new
  • copy
  • mutableCopy

另外,下列名称也意味着自己生成并持有对象。

  • allocMyObject
  • newThatObject
  • copyThis
  • mutableCopyYourObject
1
2
3
/** 自己生成并持有对象 */
id obj = [[NSObject alloc] init];
/** 自己持有对象 */

使用[NSObject new] [[NSObject alloc] init]是完全一致的。

1
2
3
/** 自己生成并持有对象 */
id obj = [NSObject new]
/** 自己持有对象 */

非自己生成的对象,自己也能持有

用alloc/new/copy/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。

1
2
3
/** 取得非自己生成并持有的对象 */
id obj = [NSMutableArray array];
/** 取得的对象存在,但自己不持有对象 */

源代码中,NSMutableArray类对象被赋值给变量obj,但变量obj自己并不持有该对象,使用retain方法可以持有对象。

1
2
3
4
5
/** 取得非自己生成并持有的对象 */
id obj = [NSMutableArray array];
/** 取得的对象存在,但自己不持有对象 */
[obj retain];
/** 自己持有对象 */

通过retain方法,非自己生成的对象跟用 alloc/new/copy/mutableCopy 方法生成并持有的对象一样,成为了自己所持有的。

不再需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法。

1
2
3
4
5
/** 自己生成并持有对象 */
id obj = [[NSObject alloc] init];
/** 自己持有对象 */
[obj release];
/** 释放对象,指向对象的指针仍然被保留在变量obj中,貌似能够访问,但对象一经释放绝不可访问 */

用alloc方法由自己生成并持有的对象就通过release方法释放了。自己生成而非自己所持有的对象,若用retain方法变为自己持有,也同样可以用release方法释放。

1
2
3
4
5
6
7
/** 取得非自己生成并持有的对象 */
id obj = [NSMutableArray array];
/** 取得的对象存在,但自己不持有对象 */
[obj retain];
/** 自己持有对象 */
[obj release];
/** 释放对象,对象不可再被访问 */

用 alloc/new/copy/mutableCopy 方法生成并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。

如果要用某个方法生成对象,并将其返回给该方法的调用方,那么它的源代码又是如何呢?

1
2
3
4
5
6
-(id)allocObject {
/** 自己生成并持有对象 */
id obj = [[NSObject alloc] init];
/** 自己持有对象 */
return obj;
}

如上例所示,原封不动的返回alloc方法生成并持有的对象,就能让调用方也持有该对象。请注意allocObject这个名称是符合前文命名规则的。

1
2
3
/** 取得非自己生成并持有的对象 */
id obj1 = [obj0 allocObject];
/** 自己持有对象 */

allocObject名称符合前文的命名规则,因此它与用alloc方法生成并持有对象的情况完全相同,所以使用allocObject方法也意味着”自己生成并持有对象”。

那么,调用[NSMutable array]方法使取得的对象存在,但自己不持有对象,又是如何实现的呢?根据上下文命名规则,不能使用alloc/new/copy/mutableCopy 开头的方法名,因此要使用object这个方法名。

1
2
3
4
5
6
7
- (id)object {
id obj = [[NSObject alloc] init];
/** 自己持有对象 */
[obj autorelease];
/** 取得的对象存在,但自己不持有对象 */
return obj;
}

上例中,我们使用了autorelease方法。用该方法可以使取得的对象存在,但自己不持有对象。autorelease 提供这样的功能,使对象在超出指定的生存范围时,能够自动并正确地释放(调用release 方法)。

使用NSMutableArray类的array类方法等可以取得谁都不持有的对象,这些方法都是通过 autorelease 而实现的。此外,根据上下文的命名规则,这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy 开头。

1
2
id obj1 = [obj0 object];
/** 取得的对象存在,但自己不持有对象 */

当然也可以通过 retain 方法将调用 autorelease 方法取得的对象变为自己持有。

1
2
3
4
id obj1 = [obj0 object];
/** 取得的对象存在,但自己不持有对象 */
[obj1 retain];
/** 自己持有对象 */

非自己持有的对象无法释放

对于用 alloc/new/copy/mutableCopy 方法生成并持有的对象,或是用 retain 方法持有的对象,由于持有者是自己,所以在不需要该对象时需要将其释放。而由此外得到的对象绝对不能释放。倘若在应用程序中释放了非自己持有的对象就会造成崩溃。例如自己生成并持有对象后,在释放完不再需要的对象之后再次释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 自己生成并持有对象 */
id obj = [[NSObject alloc] init];
/** 自己持有对象 */
[obj release];
/** 对象已释放 */
[obj release];
/**
释放之后再次释放非自己持有的对象
应用程序崩溃

崩溃情况:
再度废弃已经废弃了的对象时崩溃
访问已经废弃的对象时崩溃
*/

或者在”取得的对象存在,但自己不持有对象”时释放。

1
2
3
4
5
6
7
id obj1 = [obj0 object];
/** 取得的对象存在,但自己不持有对象 */
[obj1 release];
/**
释放了非自己持有的对象
这肯定会导致应用程序崩溃
*/

参考链接

《iOS与OS X多线程和内存管理》