ARC
内存管理(一)
想花几个章节来整理一下内存管理的知识。
我们都知道OC的内存管理用的是引用计数,下面是引用计数的思考方式。
引用计数思考方式:
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 自己持有的对象不再需要时释放
- 非自己持有的对象无法释放
用alloc/new/copy/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。
调用[NSMutableArray array]
方法使取得的对象存在,但是自己又不持有对象,实现方法如下:
- (id)object {
id obj = [[NSObject alloc] init];
[obj autorelease];
return obj;
}
autorelease提供使对象在超出指定的生存范围时能够自动并正确地释放的功能。
release会直接释放掉对象,autorelease不立即释放,而是注册到autoreleasepool中,等pool结束时自动调用release。
autorelease
很类似C语言中的局部变量,一旦局部变量超出其作用域,便自动被废弃。autorelease也是如此。与C语言不同的是,我们可以设定变量的作用域。
autorelease的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象;
- 调用已分配对象的autorelease实例方法;
- 废弃NSAutoreleasePool对象。
NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。代码表示如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //equal to [obj release]
在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。因此,我们不一定非得使用NSAutoreleasePool对象来进行开发工作。
WARNING: 当产生大量autorelease的对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放。由此可能造成内存不足。
autorelease NSAutoreleasePool对象时会出错。
通常在使用Foundation框架时,无论调用哪一个对象的autorelease实例方法,实现上是调用的都是NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,autorelease实例方法已经被重载,因此运行时会出错。
ARC
内存管理思考方式
用引用计数的思考方式也是可行的。
所有权修饰符
ARC有效时,id类型的对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。一共有四种:
- __strong
- __weak
- __unsafe_unretained
- __autorelease
__strong
__strong是id类型和对象类型默认的所有权修饰符。例如如下代码中的id变量,实际上被附加了所有权修饰符。
id obj = [[NSObject alloc] init];
//in fact it's like this
id __strong obj = [[NSObject alloc] init];
而ARC无效时,他的代码是这样的:
id obj = [[NSObject alloc] init];
//in fact it's like this
id obj = [[NSObject alloc] init];
[obj release];
__strong表示强引用。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
稍微举个简单个例子,加深一下理解
id __strong obj0 = [[NSObject alloc] init];//对象A
//obj0持有对象A的强引用,也就是自己生成的对象,自己所持有
id __strong obj1 = nil;
//obj1不持有任何对象
obj1 = obj0;
//obj1持有了由obj0赋值的A对象的强引用,这便是非自己生成的对象,自己也能持有
obj1 = nil;
//obj1释放了自己所持有的A对象,由此对A对象的持有只剩下了obj0,这是不再需要自己持有的对象时释放
//还有最后一个,非自己持有的对象无法释放,废话么不是....
看着好像一个__strong就能搞定内存管理啊,实际上没有那么简单,比如我们写block经常会遇到的循环引用的问题它就束手无策了。这时候就要用另外一个修饰符__weak。
__weak
还是来个小例子:
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (id)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
@end
//下面就是发生循环引用的状况
{
id test0 = [[Test alloc] init];//对象A
//test0持有Test对象A的强引用
id test1 = [[Test alloc] init];//对象B
//test1持有Test对象B的强引用
[test0 setObject:test1];
//Test对象A的成员变量obj_持有Test对象B的强引用
[test1 setObject:test0];
//Test对象B的成员变量obj_持有Test对象A的强引用
}
//test0变量超出其作用域,强引用失效,自动释放Test对象A
//test1变量超出其作用域,强引用失效,自动释放Test对象B
//此时持有Test对象A的强引用变量为Test对象B的成员变量obj_
//持有Test对象B的强引用为Test对象A的成员变量obj_
//发生内存泄露!!
内存泄露:所谓内存泄露就是应当废弃的对象在超出其生存周期后继续存在
好了,这就是发生循环引用的原因了。 wait a minute..其实还有一种情况叫自引用。如下:
id test = [[Test alloc] init];
[test setObject:test];
虽然只有一个对象,该对象持有其自身时,也会发生循环引用。
而__weak修饰符可以避免循环引用。__weak与__strong相反,它提供弱引用。弱引用不能持有对象实例。比如:
id __weak obj = [[NSObject alloc] init];
编译器会发出警告。因为变量obj持有对持有对象的弱引用,因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。
带__weak修饰符的变量不持有对象,在超出其变量作用域时,对象即被释放。
__weak还有一个优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态。如下:
id __weak obj1 = nil;
{
id __storng obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A: %@", obj1);
}
NSLog(@"B: %@", obj1);
执行结果如下:
A: <NSObject: 0x234e120>
B: (null)
显然,obj0在超出作用域后被废弃,obj1的值也被名正言顺地赋值为nil。
__unsafe_unretained
顾名思义,这个是不安全的所有权修饰符。
需要注意的是,尽管ARC式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。
其实这个修饰符使用场景并不多,在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来代替__weak。这里仅作了解。
update time: 2016-07-17