使用 alloc/new/copy/mutableCopy 名称开头的方法名意味着自己生成的对象只能自己持有!
// 自己生成并持有对象
id obj = [[NSObject alloc] init];
id obj1 = [NSObject new];
使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋给变量obj。另外,使用new类方法也能生成并持有对象,两中方式是完全一致的。
而copy方法则利用基于NSCopying方法约定,由各类实现的copyWithZone:
方法生成并持有对象的副本,虽说是对象副本,但同alloc、new方法一样。mutableCopy也是如此。
// 取得非自己生成的对象,但并不持有
id obj = [NSMutableArray array];
// 自己持有对象
[obj retain];
源代码中,NSMutableArray类对象被赋值给变量obj,但变量obj自己并不持有该对象,所以使用retain方法可以持有对象。
自己持有的对象,一旦不再需要,持有者有义务释放该对象。
// 自己生成并持有对象
id obj = [[NSObject alloc] init];
// 释放对象
[obj release];
如此,自己生成并持有的对象就通过release方法释放了。需要注意的是,对象一旦释放绝对不可访问。
那么,调用[NSMutableArray array]
方法取得的对象存在,但自己不持有对象是如何实现的呢?
- (id)object
{
// 自己持有对象
id obj = [[NSObject alloc] init];
// 自动释放
[obj autorelease];
return obj;
}
上例中,使用了autorelease方法。用该方法,可以使取得的对象存在,但自己却不持有对象。autorelease提供这样的功能,使对象在超出指定的生存范围时能够自动并正确地释放(调用release方法)。
调用release方法对象会立即释放。但调用autorelease方法不会立即释放,而是把对象注册到autoreleasepool中,经过一段时间后,pool结束时让里面的对象自动调用release方法,这个后面会详细说明!
对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象的时候将其释放。而由此以外所得到的对象绝对不能释放,因为释放了非自己所持有的对象就会造成崩溃!
example1:自己生成并持有的对象,在释放完后再次释放
// 自己生成并持有对象
id obj = [[NSObject alloc] init];
// 释放对象
[obj release];
// 释放之后再次释放非自己持有的对象
// 崩溃信息:访问一块坏内存
[obj release];
example2:在“取得的对象存在,但自己不持有对象”时释放
// 取得非自己生成的对象,但并不持有
id obj = [NSMutableArray array];
[obj release];
苹果对引用计数的实现是:采用引用计数表管理引用计数(下面是列举的两个好处)
所以可知,第二条在调试的时候有很重要的作用。即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被损坏,就能够确认各内存块的位置。而且在利用工具检测内存泄漏时,引用计数表的各记录也有助于检测各对象的持有者是否存在。
autorelease从名字上来看,意思就是“自动释放”,它类似于C语言中局部变量的特性。当超出其作用域时,对象实例的release实例方法会被调用。
autorelease使用如下:
而且对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
在GNUstep的源代码中,autorelease的实现为如下代码:
- (id)autorelease
{
[NSAutoreleasePool addObject:self];
}
可以看出,autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。
当调用NSObject类的autorelease实例方法时,该对象会被追加到正在使用的NSAutoreleasePool对象中的数组里.
Objective-C中为了处理对象,可将变量类型定义为id类型或各种对象类型。
ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符,一共有四种:
__strong修饰符是id类型和对象类型默认的所有权修饰符。
// 下面两句代码效果是一样的
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
__strong修饰对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的实效,引用的对象会随之释放。
{
// 自己生成并持有对象
// 因为变量obj为强引用,所以自己持有对象
id __strong obj = [[NSObject alloc] init];
}
/*
* 因为变量obj超出其作用域,强引用实效。
* 所以自动释放自己持有的对象,对象的所有者不存在,因此废弃该对象
*/
__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能正确地管理其对象的所有者,而且Objective-C类成员变量,也可以在方法参数上,使用附有__strong修饰符的变量。
所以很明显,__strong修饰符很符合“引用计数式内存管理的思考方式”。“自己生成的对象,自己持有”和“非自己生成的对象自己也能持有”只需通过对带__strong修饰符的变量赋值便可达成。通过废弃带__strong修饰符的变量或者对变量赋值,就可以做到“不再需要自己持有的对象时释放”。而由于不再键入release,所以“非自己持有的对象无法释放”根本就不可能执行。
__strong是有一个致命的缺点,那就是容易出现两个对象相互引用(循环引用容易发现内存泄漏)和一个对象自引用,而__weak就可以避免这种现象。
{
// 自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
// obj1变量持有生成对象的弱引用
id __weak obj1 = obj0;
}
/**
* 因为obj0变量超出其作用域,强引用实效,所以自动释放自己的持有的对象
* 而对象的所有者不存在,所以废弃该对象
*/
带__weak修饰符的变量不持有对象,所以在超出其变量作用域的时候,对象即被释放。
__weak修饰符还有另一个优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效,并且处于nil被赋值的状态。
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
// 输出obj1变量持有的弱引用的对象
NSLog(@"A: %@", obj1);
}
/**
* 因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象
* 对象无持有者,所以废弃该对象
*
* 废弃该对象的同时,持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1
*/
NSLog(@"B: %@", obj1);
因此,使用__weak可以避免循环引用,而且通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。
ARC式的内存管理是编译器的工作,但附有__unsafe _unretained修饰符的变量不属于编译器的内存管理对象。
__unsafe _unretained修饰符和__weak修饰符其实类似,在上面的代码中把修饰符改为__unsafe _unretained后,打印结果就为:
需要注意的是:赋值给附有__unsafe _unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃
ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。不过实际上,ARC有效时autorelease功能也是起作用的。
ARC有效时,@autoreleasePool块代替了NSAutoreleasePool类,__autoreleasing修饰符代替了autorelease方法。 而且__autoreleasing修饰符和__strong一样,一般都是非显式的附加。
注意点:编译器会自动检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasePool中,而且根据命名规则,init方法返回值的对象不注册到autoreleasePool中
在ARC有效时,[NSMutableArray array]
方法的内部实现如下:
+ (id)array
{
id obj = [[NSMutableArray alloc] init];
return obj;
}
由于return使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数返回值,编译器会自动将其注册到autoreleasePool。
而且在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasePool的对象。因为__weak修饰符持有对象的弱引用,而在访问引用对象的过程中,该对象有可能会被废弃。所以为了确保该对象存在,就会把要访问的对象注册到autoreleasePool中,那么在@autoreleasePool块结束之前都能让其存在。
在ARC有效的情况下,必须遵守一定的规则。规则如下: