ARC在编译期间,根据Objective-C对象的存活周期,在适当的位置添加retain和release代码。从概念上讲,ARC与手动引用计数内存管理遵循同样的内存管理规则,但是ARC也无法防止循环强引用。
ARC模式下的修饰符来修饰变量和声明属性:
- 声明变量的修饰符:__strong, __weak, __unsafe_unretained, __autoreleasing
- 声明属性的修饰符:strong, weak, unsafe_unretained,assign
- 对象和CoreFoundation对象直接的转换修饰符号:__bridge,__bridge_retained或CFBridgingRetain, __bridge_transfer或CFBridgingRelease。
- 对于线程的安全,有nonatomic,这样效率就更高了,但是不是线程的。如果要线程安全,可以使用atomic,这样在访问是就会有线程锁。
这里主讲iOS ARC所以就只讲weak strong assign这三个修饰符,__weak __strong __assign跟前面的一样
__strong 修饰符
先来看一段代码:
NSObject *obj1 = [[NSObject alloc]init];
id obj2 = obj1;
__strong 修饰符是id类型和对象类型默认的所有权修饰符,也就是说上面的变量,实际上附加了所有权修饰符,id和对象类型在没有明确的指定所有权修饰符时,默认为__strong修饰符,上面代码实质上跟下面的代码一致
__strong NSObject *obj1 = [[NSObject alloc]init];
__strong id obj2 = obj1;
通过__strong修饰符,不必再次键入retain或者release,完美地满足了“引用计数内存管理的思考方式”:</p>
自己生成的对象,自己持有
非自己生成的对象,自己也能持有
不再需要自己持有的对象时释放
非自己持有的对象无法释放
事列代码
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = obj1;
NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
obj1 = nil;
NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
输出为:<p>
obj1:<NSObject: 0x100203ab0>,obj2=<NSObject: 0x100203ab0></p>
obj1:(null),obj2=<NSObject: 0x100203ab0>
__weak修饰符
看起来好像通过 __strong 修饰符编译器就能完美地进行内存管理,大师遗憾的是,仅通过__strong修饰符时不能解决有些重大问题的。
这里提到的重大问题就是引用计数试内存管理中必然会发生的“循环引用”的问题
例如前面出现的带有__strong修饰符的成员变量在持有对象时,很容易发生循环引用
@interface Test : NSobject {
id __strong _obj;
}
- (void)setObj:(id)obj;
@end
@implementation Test
- (void)setObj:(id)obj {
_obj = obj;
}
@end
以下就为循环引用
int main() {
id test1 = [[Test alloc]init];/*对象a*/
//test1持有Test对象a的强引用
id test2 = [[Test alloc]init];/*对象b*/
//test2持有Test对象b的强引用
[test1 setObj:test2];
/*
test1对象a的obj成员变量持有test2对象b的强引用
此时,持有test对象b的强引用的变量为Test对象a的_obj和test2
*/
[test2 setObj:test1];
/*
test2对象b的obj成员变量持有test2对象a的强引用
此时,持有test对象b的强引用的变量为Test对象b的_obj和test1
*/
/*
因为test1变量超出其作用域,强引用失效,所以自动释放对象a
因为test2变量超出其作用域,强引用失效,所以自动释放对象b
此时,持有test对象a的强引用的变量为test2对象b的_obj
此时,持有test对象b的强引用的变量为test1对象a的_obj
内存泄漏出现了,两个对象超出作用域都没有被释放
*/
return 0;
}
下面这种情况,虽然只有一个对象,但在该对象持有其自身是也发生了循环引用
int main(){
id test = [[Test alloc]init];
[test setObj:test];
//对自身的强引用,自引用
return 0;
}
__weak id obj = [[NSObject alloc]init];
//实际上这样的代码是会报警告
//此代码将自己生成的并吃藕的对象赋值给附有 __weak修饰符的变量 obj。即变量obj持有对持有对象的弱引用,因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。编译器对此会给出警告。如果想下面这样,将对象赋值给一个strong修饰符的变量后再赋值给附有weak修饰符的变量,就不会发生警告了。
{
id __strong obj0 = [[NSObject alloc]init];
id __weak obj1 = obj0;
}
因为带__weak修饰符的变量不持有对象,所以超出其变量作用域是,对象即呗释放。如果像下面这样将先前可能发生循环引用的类成员变量改成附有__weak修饰符的成员变量的话,该现象则可以避免
@interface Test : NSObject
{
id __weak _obj;
}
@end
__weak修饰符还有另一有点。在持有某对象的弱引用时,若该对象被废弃,则此引用将自动失效且处于nil被赋值状态(空弱引用)。即下代码
NSObject *obj1 = [[NSObject alloc]init];
__weak NSObject *obj2 = obj1;
NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
obj1 = nil;
NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
输出结果为
obj1:<NSObject: 0x100202e50>,obj2=<NSObject: 0x100202e50>
obj1:(null),obj2=(null)
使用__weak修饰符可避免循环引用,通过检查附有__weak修饰符变量
__weak修饰符智能用于iOS5以上及OS XLion以上版本的应用程序,iOS4以及OS X Snow Leopard 的应用程序中可以使用__unsafe_unretained来代替。
__unsafe_unretained 修饰符
__unsafe_unretained修饰符正如其名unsafe所示,不安全的所有权修饰符。尽管arc模式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时一定要注意<p>
id __unsafe_unretained obj = [[NSObject alloc] init];<p>
<html>
该代码将自己生成并持有的对象赋值给附有__unsafe_unretained修饰符的变量中,虽然使用了unsafe的变量,但是编译器并不会忽略,而是给出适当的警告。<p>
</html>
warning:Assigning retained object to unsafe_unretained variable; object will be released after assignment
附有__unsafe_unretained修饰符的变量同附有__weak修饰发的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,__unsafe_unretained修饰符和__weak修饰符时一样的,下面我们来看一看差异。
__unsafe_unretained id obj1 = nil;
({
id obj2 = [[NSObject alloc] init];
obj1 = obj2;
NSLog(@"obj1:%@,obj2:%@",obj1,obj2);
});
NSLog(@"obj1:%@",obj1);
代码执行结果为
obj1:<NSObject: 0x1003007c0>,obj2:<NSObject: 0x1003007c0>
obj1:<NSObject: 0x1003007c0>
<html>
发生了什么,来看一看
</html>
__unsafe_unretained id obj1 = nil;
({
/** 自己生成并持有对象*/
id obj2 = [[NSObject alloc] init];
/**
*因为obj2变量为强引用,所以自己持有对象
*/
obj1 = obj2;
/**虽然obj2变量赋值给了obj1,但是obj1变量即不持有对象的强引用也不持有弱引用
*/
NSLog(@"obj1:%@,obj2:%@",obj1,obj2);
});
/** 因为obj2变量超出其作用域,所以自动释放自己持有的对象
* 此时对象无持有者,所以废弃该对象
*/
NSLog(@"obj1:%@",obj1);
/**
*输出obj1比那两表示的对象
*obj1变量表示的对象已经被废弃(悬垂指针)!错误访问!
*/
也就是说,最后一行的NSLog只是碰巧正常运行而已。虽然访问了已经被废弃的对象,但是应用程序在个别运行状态才会崩溃。
在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在。
但是,在使用前,让我们在一尺想想为什么需要使用附有__unsafe_unretained修饰符变量。
比如在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来替代__weak修饰符。赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其实际存在,那么应用程序就会崩溃。
__autoreleasing修饰符
ARC有效是autorelease会如何呢,不能使用autorelease方法。另外,也不能使用NSAutoreleasePool类。这样一来,虽然autorelease无法直接使用,但实际上,ARC有效时autorelease功能起作用的
//ARC无效
{
NSAutoreleasePool *pool = [[NSAutorelease alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool autorelease];
}
//ARC有效
{
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc]init];
}
}
指定“@autoreleasepool块”来替代“NSAutoreleasePool类对象生成、持有以及废弃”这一范围。
另外,ARC有效是,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。对象赋值给附有__autoreleasing对象修饰符的变量等价于ARC五小时调用对象那个的autorelease方法,即对象被注册到autoreleasepool。
也就是说可以理解为,在ARC有效时,用@autoreleasepool块来替代NSAreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。
assign属性修饰符跟__unsafe_unretained一致,用来修饰基础数据类型