文章目录
一.引言
1.1 概要
ARC,全称叫Automatic Reference Counting。就像自动引用计数,ARC只是自动地帮我们处理“引用计数”的相关部分。
在编译单位上,可设置ARC有效或无效,Xcode4.2默认设定为对所有文件ARC有效
1.2 学习ARC之前要了解的基础知识
- 引用计数(也就是内存管理)基础
每个OC对象都拥有自己的引用计数器,是一个整数,可以理解为对象此时被引用的次数也可以理解为有多少人正在使用此对象,一个对象被创建默认引用计数为1.
- 可能引起的四种变化
- 自己生成的对象,自己所持有
- 非自己生成的对象,自己也能持有
- 自己持有的对象不再需要时释放
- 非自己持有的对象无法释放
(个人感觉array应该是非自己生成并持有)
2. MRC是在ARC没有出现前采用的管理引用计数的方法,简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码(MRC)可以自动地由编译器完成了。
关于MRC和ARC之间的区别,具体可以参考这篇
1.3 所有权修饰符
ARC有效时,id类型和对象类型必须附加所有权修饰符,所有权修饰符一共有4种
- _strong 修饰符
- _week
- unsafe_unretained
- autoreleasing
_strong修饰符
_strong修饰符是id类型和对象类型默认的所有权修饰符。也就是说,一下源代码中的id变量,实际上被附加了所有权修饰符
- 取得自己生成并持有的对象
- 在ARC有效的情况下
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
//这两种表述方式一样(在ARC有效的情况下)
- 在ARC无效的情况下
id obj = [[NSObject alloc] init];
[obj release];
//为了释放生成并持有的对象,增加调用release方法的代码
如上所示,__strong修饰符的变量obj在超出其变量作用域时(即在该变量被废弃时),会释放其被赋予的对象(ARC有效时自动释放,ARC无效时类似MRC,调用release方法释放)
__strong修饰符表示对对象的“强引用”
//在这里对象所有者和对象的生存周期也是明确的
//自己生成并持有对象alloc
id __strong obj = [[NSObject alloc] init];
//strong表示变量obj为强引用,所以自己持有对象
//因为变量obj超出其作用域,强引用失效,所以自动释放(对象的所有者不存在因此废弃该对象)
- 取得非自己生成并持有的对象
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象
//因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。(无废弃)
- 附有__strong修饰符的变量之间可以相互赋值
可以得出,__strong修饰符的变量,在变量作用域和赋值过程中都能够正确地管理其对象的所有者。
//这其中有两个对象,对象A和对象B,两个都属于NSObject类
//有三个变量为obj0,obj1,obj2.且都为强引用变量
//两者之间的关系为,变量(obj)持有对象(A或B)。 当对象的所有者不存在时,就会被废弃。
id __strong obj0 = [[NSObject alloc] init];//对象A
//obj0持有对象A的强引用
id __strong obj1 = [[NSObject alloc] init];//对象B
//obj1持有对象B的强引用
id __strong obj2 = nil;
//obj2不持有任何对象
obj0 = obj1;
//obj0持有由obj1赋值的对象B的强引用
//因为obj0被赋值,所以原来持有的对对象A的强引用失效
//对象A的所有者不存在,因此废弃对象A
//此时,持有对象B的强引用的变量为obj0和obj1.
obj2 = obj0;
//obj2持有由obj0赋值的对象B的强引用
//此时,持有对象B的强引用的变量为obj0,obj1和obj2.
obj1 = nil;
//因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,持有对象B的强引用变量为obj0和obj2.
obj0 = nil;
//因为nil被赋予了obj0,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量为obj2
obj2 = nil;
//因为nil被赋予了obj2,所以对对象B的强引用失效
//对象B的所有者不存在,因此废弃对象B。
- 可使用于类成员变量以及方法参数中。
Test.h
@interface Test : NSObject
{
id __strong obj_;
}
-(void)setObject:(id __strong)obj;
@end
Test.m
@implementation Test
- (id) init
{
self = [super init];
return self;
}
- (void) setObject:(id __strong)obj
{
obj_ = obj;
}
@end
mian.m
{
id __strong test = [[Test alloc] init];
//test持有Test对象的强引用
[test setObject:[[NSObject alloc] init]];
//Test对象的obj成员,持有NSObject对象的强引用
}
//因为test变量超出其作用域,强引用失效,所以自动释放Test对象
//Test对象的所有者不存在,因此废弃该对象
//废弃Test对象的同时,Test对象的obj_成员也被废弃,NSobject对象的强引用失效(说明成员变量的周期与对象是同步的)
//自动释放NSObject对象,NSObject对象的所有者不存在,因此废弃该对象
- 可以保证将附有这些修饰符的自动变量初始化为nil。(__strong, __weak,__autoreleasing都可)
id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;
//等同于
id __strong obj1 = nil;
id __weak obj2 = nil;
id __autoreleasing obj3 = nil;
- __strong可能引起的四个变化的思考
通过__strong修饰符(ARC方式)不必再次键入retain或者release,完美地满足了“引用计数式内存管理的思考方式”
- 自己生成的对象,自己所持有 即第一点,对带__strong 修饰符的变量赋值便可达成
- 非自己生成的对象,自己也能持有 即第二点
- 自己持有的对象不再需要时释放 通过废弃带__strong修饰符的变量(结束变量作用域)或者对变量赋值
- 非自己持有的对象无法释放 不必键入release,所以原本就不会执行。
__weak修饰符
- __strong修饰符的成员变量在持有对象时,很容易发生循环引用
{
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的强引用
//此时,持有Test对象B的强引用变量为Test对象A的obj_和test1.
[test1 setObject:test0];
//Test对象B的obj_成员变量持有Test对象A的强引用
//此时,持有Test对象A的强引用变量为Test对象B的obj_和test0.
}
//test1和test0在超出作用域后,强引用失效,自动释放对象TestA和B
//但是test对象A的obj_和test对象的obj_ 都没有释放(对象的持有者没有全部被释放,所以对象还没有被废弃)
//这样容易发生内存泄漏
- 内存泄漏:简单来说,内存泄漏就是在内存该被释放的时候没有释放,导致内存被浪费使用了
内存泄漏在iOS开发中轻则影响性能,重则导致crash
循环引用使得对象不能被再次废弃,在该对象持有其自身时,也会发生循环引用(对自身的强引用)
id test = [[Test alloc] init];
[test setObject:test];
使用__weak可以避免循环引用
弱引用不能持有对象实例
//若将对象赋值给附有__strong修饰符的变量之后再赋值给附有__weak修饰符的变量,就不会警告了
id __strong obj0 = [[NSObject alloc] init];
//自己生成并持有对象(强引用)
id __weak obj1 = obj0;
//obj1变量持有生成对象的弱引用
//因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象。因为对象的所有者不存在,所以废弃该对象
修改成弱引用,即可避免
@interface Test : NSObject
{
//id __strong obj_;
id __weak obj_;
}
-(void)setObject:(id __strong)obj;
@end
- 在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)
通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"%@", obj1);
//输出obj1变量持有的弱引用的对象
}
NSLog(@"%@", obj1);
//因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象。
//因为对象无持有者,所以废弃该对象
//废弃对象的同时,弱引用的obj1变量的弱引用失效,nil赋值给obj1.
//输出赋值给obj1变量中的nil
编译结果:
ARC和MRCtest[6932:406639] <NSObject: 0x10073d420>
ARC和MRCtest[6932:406639] (null)
__unsafe_unretained修饰符
__unsafe__unretained 是不安全的所有权修饰符,附有__unsafe__unretained 修饰符的变量不属于编译器的内存管理类对象。
- 附有__unsafe__unretained 修饰符的变量同附有__weak修饰符的变量一样,自己生成并持有的对象不能继续为自己所用,所以生成的对象会立即被释放
id __unsafe_unretained obj1 = nil;
{
//自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
//obj变量为强引用,所以自己持有对象
obj1 = obj0;
//obj0赋值给obj1,但是obj1变量既不持有对象的强引用也不持有弱引用
NSLog(@"%@", obj1);
//obj1变量表示的对象
}
//因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象。
//因为对象无持有者,所以废弃该对象
NSLog(@"%@", obj1);
//输出obj1变量表示的对象
//obj0变量表示的对象,已经被废弃(悬垂指针)错误访问!(程序可能会崩溃)
编译结果:
ARC和MRCtest[7593:429977] <NSObject: 0x10062c480>
ARC和MRCtest[7593:429977] <NSObject: 0x10062c480>
在使用_unsafe__unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在。
赋值给附有_unsafe__unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么程序就会崩溃。
__autoreleasing修饰符
-
ARC有效时不能使用autorelease方法,也不能使用NSAutoreleasePool类。但是ARC有效时autorelease功能是起作用的。
-
其实在ARC有效时,用@autoreleasePool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法
-
对比MRC(autorelease会在超出作用域时,调用对象的release实例方法,
autorelease方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变。autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release) -
生成并持有NSAutoreleasePool对象。
调用已分配对象的autorelease方法。【将对象注册到pool中】
废弃NSAutoreleasePool对象。【pool执行drain废除,其中的对象也跟着release】
- 一般使用__autoreleasepool修饰符都是非显式的
- 用__strong修饰符取得非自己生成并持有的对象
编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值对象注册到autoleasepool(这同在ARC无效时取得调用了autorelease方法的对象是一样的)也有猜想说当不可变对象以alloc/new/copy/mutableCopy开始时,进行浅拷贝,并没有调用autorelease方法,而是调用了copy方法。
@autoreleasepool {
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象
//且该对象由编译器判断其方法名后,自动注册到autoreleasepool
}
//变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象
//随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象呗自动释放
//因为对象的持有者不存在,所以废弃对象
这样不使用即非显式地使用__autoreleasepool修饰符也能使对象注册到autoleasepool
因为没有显式制定所有权修饰符,当return使对象变量超出其作用域,对应的自己持有的对象会被自己自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool
- 在访问附有_weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象
id __weak wObj = sObj;
NSLog(@"%@", [wObj class]);
//等同于
id __weak wObj = sObj;
id __autoreleasing tmp = wObj;
NSLog(@"%@", [tmp class]);
因为弱引用不持有,在访问对象的过程中对象有可能被废弃,要保证正常访问就需要将其注册到自动释放池中,确保在@autoreleasepool块结束之前该对象存在。
- id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符
例如,为了详细的得到错误信息,经常在方法的参数中传递NSError对象的指针,而不是返回值
如performOperationWithError方法
该方法的声明为
- (BOOL) performOperationWithError:(NSError **)error;
//等同于
- (BOOL) performOperationWithError:(NSError * __autoreleasing *)error;
使用附有__autoreleasing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。
- (BOOL) performOperationWithError:(NSError * __autoreleasing *)error 「
*error = [NSError alloc]initwithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
NSError * __autoreleasing* 类型的error作为*error被赋值,所以能够返回注册到autoreleasepool中的对象。
赋值给对象指针时,所有权修饰符必须一致
NSError *error = nil;
NSError * __strong *pError = &error;
//其他所有权修饰符也是一样
NSError __weak *error = nil;
NSError * __weak *pError = &error;
NSError __unsafe__unretained *error = nil;
NSError * __unsafe__unretained *pError = &error;
//NSError *__autoreleasing error;
//在这里,加上__autoreleasing之后,相当于在MRC中对返回值error做了如下事情:
//*error = [[[NSError alloc] init] autorelease];
//error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心error指向对象的释放。
NSError __strong *error = nil;
BOOL result = [obj performOperationWithError:&error];
//等同于(编译器自动转化)
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;
- 显式地指定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量,函数以及方法函数)