ARC的内存管理方式
什么是ARC
ARC(自动引用计数)是一个编译期技术,介于垃圾回收(GC)和MRC之间,ARC让程序员不再需要书写retain/release/autorelease
语句,runtime会维护一张引用计数表,编译器会在编译期在合适的位置自动给用添加retain/release/autorelease
,它的特点是自动引用技术简化了内存管理的难度
ARC内存管理的思考方式:
ARC内存管理的思考方式与MRC一致。
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 自己持有的对象,不再需要时,释放
- 非自己持有的对象无法释放
ARC代码编写规则
- 不能在程序中定义和使用:retain、release、autorelease和retainCount
- 使用@autoreleasepool代替NSAutoreleasePool
- 不用在dealloc中释放实例变量(可以在dealloc中释放资源),也不需要调用
[super dealloc]
所有权修饰符
id类型:用于隐藏对象类型的类名部分,类似于C语言中的void*;
ARC有效时,id类型和对象类型必须加上所有权修饰符:
-
__strong
修饰符 -
__weak
修饰符 -
__unsafe_unretained
修饰符 -
__autoreleasing
修饰符
__strong修饰符
__strong
修饰符是默认的修饰符
//两种方式是等价的
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init]
特性:strong表示强引用,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
- 与自己生成并持有对象
//ARC有效
{
//自己生成,自己持有,obj为强引用,持有变量
id __strong obj = [[NSObject alloc]init];
//todo
.......
}
//超出作用域,强引用失效
//以上代码在ARC无效下等价于
{
id obj = [[NSObject alloc]init];
//todo
......
[obj release];
}
2、取得非自己生成但持有的对象
//取得非自己生成的对象,obj为强引用,所以持有该对象
id __strong obj = [NSArray array];
3、附有__strong
修饰符的变量可以相互赋值
//obj0持有对象A的强引用
id __strong obj0 = [[NSObject alloc]init];
//obj1持有对象B的强引用
id __strong obj1 = [[NSObject alloc]init];
//obj2不持有任何对象
id __strong obj2 = nil;
/*
* obj0持有obj1赋值的对象B的强引用,对象B的持有者为obj0、obj1
* obj0被赋值,原先持有的对象A的强引用失效,对象A的所有者不存在,因此对象A废弃
*/
obj0 = obj1;
/*
* obj2持有obj0赋值的对象B的强引用
* 对象B的持有者为:obj0、obj1、obj2
*/
obj2 = obj0;
4、附有__strong
修饰符的变量作为方法的参数也能正确的管理其对象的所有者
总结:通过__strong
修饰符,不必再次键入retain或release,完美地满足了“引用计数式内存管理的思考方式”。
__weak修饰符
__weak
修饰符,提供弱引用,弱引用不能持有实例对象,可以避免循环引用
/*
* 编译器会给警告
* obj为弱引用,并不持有对象,生成的对象会被立即释放,所以会给警告
*/
id __weak obj = [[NSObject alloc]init];
//obj0强引用持有对象,obj1弱引用不持有对象
id __strong obj0 = [[NSObject alloc]init];
id __weak obj1 = obj0;
注: 在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效,且处于nil被赋值的状态(空弱引用)
__unsafe_unretained修饰符
__unsafe_unretained
是不安全的修饰符,修饰的变量不属于编译器的内存管理对象,同__weak
修饰符一样,不能持有实例对象
/*
* 编译器警告,obj不持有NSObject对象
*/
id __unsafe_unretained obj = [[NSObject alloc]init];
id __unsafe_unretained obj1 = nil;
{
//obj0强引用NSObject对象
id __strong obj0 = [[NSObject alloc]init];
//obj1不持有NSObject对象
obj1 = obj0;
NSLog(@"A:%@",obj1);
}
/*
* obj0变量超出作用域,强引用失效,释放持有变量
* obj0指向的对象被废弃(悬垂指针),成为僵尸对象
*/
NSLog(@"B:%@",obj1);
执行结果:
A: <NSObject: 0x753e180>
B: <NSObject: 0x753e180>
最后一行NSlog,碰巧正常运行。因为obj0指向的对象已经被废弃,不能被访问
为什么要有__unsafe_unretained
修饰符?
在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_retained
修饰符来代替__weak
,__unsafe_unretained
修饰的对象赋值给__strong
修饰符的变量时,必须保证被赋值对象确实存在。
__autoreleasing修饰符
ARC无效时
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];
ARC有效时,代码写为
//ARC指定“@autoreleasepool块”来替代“NSAutoReleasePool类对象生成、持有以及废弃”这一范围
@autoreleasepool {
//添加__autoreleasing修饰符来代替调用autorelease方法
id __autoreleasing obj = [[NSObject alloc]init];
}
非显示使用__autorelease
的情况
__autoreleasing
和__strong
一样通常都不会显示添加。
- ARC有效时,在取得非自己生成并持有的对象时,编译器会检查方法名是否以
alloc/new/copy/mutableCopy
开始,如果不是则自动将返回的对象值注册到autoreleasepool中。
@autoreleasepool {
// 变量为强引用,所以自己持有对象
//但编译器判断其方法名后,自动将其注册到autoreleasepool
id __strong obj = [NSMutableArray array];
}
- 非
alloc/new/copy/mutableCopy
开头的非自己生成但持有的方法中定义的对象
+ (id)array
{
//obj生成并持有对象,强引用
id obj = [[NSMutableArray alloc]init];
//强引用在obj超出作用域后会释放对象,但是该对象又作为返回值不能被释放,所以编译器会自动注册到autoreleasepool中
return obj;
}
//得到的是注册到autoreleasepool中的对象
id obj = [NSMutableArray array];
- 访问
__weak
修饰的对象,实际上访问的是注册到autoreleasepool中的对象
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class])
//等价于
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@", [tmp class]);
__weak
修饰的对象为弱引用,在访问时可能会随时被废弃,只要把对象注册到autoreleasepool中,就能在@autoreleasepool块结束前确保对象存在
- id的指针或者NSObject的指针没有显示指定时,会被默认被附上
__autoreleasing
修饰符
//id的指针
id *obj;
id __autoreleasing *obj;
//对象的指针
NSError **error;
NSError * __autoreleasing *error;
- (BOOL)performOperationWithError:(NSError **)error;
//error为对象指针,所以默认修饰符是__autoreleasing;
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error {
*error = [[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil];
return NO
}
这里- (BOOL)performOperationWithError:(NSError **)error;
与除alloc/new/copy/mutableCopy外的其他非自己生成但持有的方法一样,{}里[[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil]
生成的对象因为超出作用域后会释放对象,所以编译器会将其放入到autoreleasepool中,所以这里的alloc方法生成的对象(理论上是__strong
修饰的对象)能够正常赋值给__autoreleasing
修饰的*error
注:赋值给对象指针时,所有权修饰符必须一致
//默认是__strong
NSError *error = nil;
//默认是__autoreleasing
NSError **pError = &error; //报错
NSError * __autoreleasing *pError1 = &error; //不报错
//不报错
NSError _strong *error = nil;
BOOL result = [obj performOperationWithError:&error];
上面的error是__strong
修饰的,但是performOperationWithError:
里的参数是__autoreleasing
修饰的,但是没有因修饰符不一致而报错,原因是编译器做了如下转换:
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
注:显示指定
__autoreleasing
时要注意,对象变了要为自动变量(包括局部变量、函数以及方法参数)
总结:
-
__strong
、__weak
以及__autoreleasing
修饰的变量初始值默认为nil - 自己生成并持有的对象,即以alloc/new/copy/mutableCopy开头的方法生成的对象,编译器默认给变量添加修饰符
__strong
- 非自己生成但持有的对象,编译器默认给变量添加修饰符
__autoreleasing
- 非自己生成但持有对象的方法中返回的值默认是
__autoreleasing
- id指针和对象指针默认是
__autoreleasing
-
__weak
用于避免循环引用,在对象废弃时,自动降变量置为nil -
__unsafe_unretained
在iOS4中用来替代__weak
,但是在对象被废弃时,不会将变量置为nil,因此在将__unsafe_unretained
修饰的变量赋值给__strong
修饰的变量时要确保对象是否存在 - 修饰符是ARC时才用的,MRC没有修饰符
- 修饰符用于实例变量、自动变量等变量的声明,不能用于属性
参考资料:
- Objective-C高级编程iOS与OS X多线程和内存管理