1. 所有权修饰符
ARC有效时,id类型和对象类型上必须附加所有权修饰符:
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
__strong
__strong修饰符是id类型和对象类型的默认所有权修饰符,在没有指定明确的所有权修饰符时,默认为__strong修饰符。
id obj = [[NSObject alloc] inti];
//等效于
id __strong obj = [[NSObject alloc] inti];
__strong表示对对象的强引用,用它修饰的变量在超出作用域时,会释放其被赋予的对象。
{
//自己生成并持有对象
id __strong obj = [[NSObject alloc] init];
}
//变量obj超出其作用域,强引用失效,自动释放自己持有的对象,
//对象的持有者不存在,废弃该对象。
__strong修饰的变量,在赋值上也能正确地管理其对象的所有者。
id __strong obj0 = [[NSObject alloc] init]; //对象A的持有者为obj0
id __strong obj1 = [[NSObject alloc] init]; //对象B的持有者为obj1
id __strong obj2 = nil;
obj0 = obj1; //对象A的持有者不存在,废弃对象A。对象B的持有者变为obj0和obj1。
obj2 = obj0; //对象B的持有者变为obj0,obj1和obj2。
obj1 = nil; //对象B的持有者变为obj0和obj2。
obj0 = nil; //对象B的持有者变为obj2。
obj2 = nil; //对象B的持有者不存在,废弃对象B。
通过__strong修饰符,不必再次键入retain和release。对带__strong修饰符的变量赋值就可以持有对象,废弃带__strong修饰符的变量或对变量赋值就可以释放对象。
__weak
仅使用__strong修饰符,有些问题无法解决,那就是引用计数式内存管理中必然会发生的循环引用问题。__strong修饰符的成员变量在持有对象时,很容易发生循环引用。
@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 obj0 = [[Test alloc] init]; //对象A
id obj1 = [[Test alloc] init]; //对象B
[obj0 setObject:obj1];
[obj1 setObject:obj0];
}
//虽然obj0和obj1超出作用域后释放了各自持有的对象,
//但是还有对象A的obj_强引用对象B,对象B的obj_强引用对象A,
//所以A和B不会被废弃,发生了内存泄漏。
这时候就要使用__weak修饰符,它与__strong相反,提供弱引用,不能持有对象实例。将上面的代码修改一下就不会发生循环引用了:
@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id _strong)obj;
@end
__weak修饰符还有一个优点,在持有某个对象的弱引用时,若对象被废弃,此弱引用将自动被赋值为nil,通过检查__weak修饰的变量是否为nil可以判断对象是否已废弃。
__unsafe_unretained
顾名思义,__unsafe_unretained是不安全的所有权修饰符,尽管ARC式的内存管理是编译器的工作,但__unsafe_unretained修饰的变量不是编译器的内存管理对象。__unsafe_unretained修饰的变量和__weak修饰的变量一样都不持有对象,区别是它不会像__weak修饰的变量一样在对象被废弃时赋值为nil,所以可能形成野指针。
id __unsafe_unretained obj0 = nil;
{
//自己生成并持有对象
id __strong obj1 = [NSObject alloc] init];
//虽然obj1赋值给了obj0,但obj0并不持有对象
obj0 = obj1;
NSLog(@"A: %@", obj0);
}
//obj1超出作用域,强引用失效,释放自己持有的对象,对象无持有者,废弃该对象,
//所以obj0表示的对象已被废弃,obj0变成野指针。
NSLog(@"A: %@", obj0);
__autoreleasing
ARC下不能使用autorelease方法,也不能使用NSAutoreleasePool类,但实际上autorelease功能是起作用的。使用@autoreleasepool块替代NSAutoreleasePool类,用__autoreleasing修饰的变量替代autorelease方法:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
但是,显示地附加__autoreleasing和显示地附加__strong一样罕见。取得非自己生成并持有的对象时,对象已经被自动注册到autoreleasepool,因为编译器会检查方法名是否以alloc/new/copy/mutableCopy开头,如果不是则自动将返回值的对象注册到autoreleasepool。另外,init方法返回的对象不注册到autoreleasepool。
@autoreleasepool {
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
/*
由于obj是强引用,所以自己持有对象,
并且编译器在判断方法名后,将对象注册到autoreleasepool
*/
}
/*
obj超出作用域,自动释放持有的对象
同时,autoreleasepool块结束,注册到其中
的所有对象自动释放。
对象的持有者不存在,废弃对象。
*/
访问__weak修饰的变量时,实际上必定要访问注册到autoreleasepool的对象。因为__weak修饰的变量只持有对象的弱引用,在访问对象的过程中,该对象有可能被废弃,注册到autoreleasepool,那么在@autoreleasepool块结束前都能确保该对象存在。
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class]);
等同于
id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class = %@", [temp class]);
id的指针和对象的指针在没有指定所有权修饰符时,会被加上__autoreleasing修饰符。比如,为了得到详细的错误信息,经常会在方法的参数中传递NSError对象的指针,而不是返回值。
- (BOOL)performOperationWithError:(NSError **)error;
//等同于
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
该方法使用如下:
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];
使用该方法发生错误应该是这样:
- (BOOL)performOperationWihError:(NSError * __autoreleasing *)error
{
//发生错误
*error = [NSError errorWithDomain:MyAppDomain error:errorCode userinfo:nil];
return NO;
}
需要注意的是,赋值给对象指针时,所有权修饰符必须一致:
NSError *error0 = nil;
NSError **pError0 = &error0;
//编译错误
NSError *error1 = nil;
NSError * __strong *pError1 = &error1;
//编译正常
NSError __weak *error2 = nil
NSError * __weak *pError2 = &error2;
//编译正常
2. ARC规则
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显示调用dealloc
- 使用@autoreleasepool块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显示转换id和void *
参考:《Objective-C高级编程 iOS与OS X多线程和内存管理》