ARC是什么?顾名思义,自动引用计数。
苹果的OC中采用了此机制,编译器可以帮助程序员来进行内存管理,无需键入retain,release代码。
最近简单学习了一下ARC,简单记录一下。
引用计数式内存管理的思考方式就是思考ARC所引起的变化。
自己生成的对象,自己所持有。
非自己生成的对象,自己也能持有。
自己持有的对象不再需要时释放。
非自己持有的对象无法释放。
这一思考方式在ARC也是可行的,下面理解一下ARC中追加的所有权声明。
当ARC有效时,id类型和对象类型必须附加所有权修饰符,一共有如下四种。
__strong
__weak
__unsafe_unretained
__autoreleasing
该类型的修饰符是id和对象类型的默认修饰符。
id obj = [[NSObject alloc] init];
//在没有明确指定所有权修饰符时,默认为__strong
id __strong obj = [[NSObject alloc] init];
当ARC无效时,该源码如下:
{
id obj = [[NSObject alloc] init]
[obj release];
//为了释放生成并持有的对象,增加了调用release方法,等同于ARC有效时的动作。
}
__strong
修饰符表示对对象的强引用。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
下面是关于对象所有者的部分。
id __strong obj = [[NSObject alloc] init];
//源码实现如下
{
//自己生成并持有对象
id __strong obj = [[NSObject alloc] init];
//因为变量obj为强引用,所以自己持有对象
}
//变量obj超出其作用域,强引用失效
//自动释放持有的object对象
//对象的所有者不存在,因此废弃该对象
id __strong obj = [[NSMutableArray alloc] init];
//源码实现如下
{
//取得非自己生成并持有的对象
id __strong obj = [[NSMutableArray alloc] init];
//因为变量obj为强引用,所以自己持有对象
}
//因为变量超出其作用域,强引用失效
//所以自动释放自己持有的对象
//对象的所有者不存在,因此废弃该对象
__strong修饰符不仅仅只在变量作用域中,在赋值上也可以正确的管理其对象的所有者。
当然即使是成员变量我们也可以使用修饰符,方法参数也可。
注意__strong修饰符和后面_weak修饰符和__autoreleasing修饰符一样,可以保证将附有这些修饰符的自动变量初始化为nil。
id类型和对象类型的所有权修饰符默认为_strong修饰符,所以不再需要写上"__strong"
看似我们前面提到的__strong修饰符很完美,可以解决所有问题,但很遗憾,他不能解决有些重大问题。
这个重大问题就是引用计数式内存管理中必然会发生的循环引用问题。
循环引用容易发生内存泄漏,所谓内存泄漏就是应当废弃的对象在超出其生命周期后继续存在
循环应用发生条件
两个对象互相强引用对方
在该对象持有其自身时(自己引用自己)
下面举个例子:
@interface Test:NSObject {
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
id test1 = [[Test alloc] init]; //对象B
[test0 setObject: test1]; //test0持有TestA的强引用。其成员变量持有TesrB的强引用
[test1 setObject: test0]; //同理
/*
超出变量作用域。
TestA的持有者test0失效。
TestB的持有者test1失效。
此时TestA被TestB的成员变量持有。
TestB被TestA的成员变量持有。
俩者相互持有,没有释放,内存泄漏。
*/
在这个例子中,代码的本意是,在两个变量超出作用域时废弃,但由于两个对象互相强引用导致内存泄露——内存泄漏就是应当废弃的对象在超出其生命周期后继续存在。
怎样才能避免循环应用,这个时候就提出了__weak修饰符,提供弱引用,不能持有对象实例。
下面看看代码:
这里会出现警告,我们可以将对象赋值给带有__strong修饰符的变量之后再赋值给weak,就不会发生警告了。
因为该修饰符不持有对象,所以在超出作用域时会被立即释放。将之前循环引用中成员变量改成如下,实现循环弱引用:
@interface Test:NSObject {
id __weak obj_;
}
__weak修饰符还有另外一个优点,在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态。
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A:%@", obj1);
}
NSLog(@"B:%@",obj1);
//输出结果为:
//A:
//B:(null)
//下面分析一下
{
//自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
//因为obj0变量为强引用,所以自己持有对象
obj1 = obj0;
//obj1对变量obj0持有弱引用
}
//因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为对象无人持有,所以废弃该对象
//废弃对象的同时,持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1;保存自己生成并持有的对象,生成的对象会立即被释放
结果如下:
使用__weak修饰符可避免循环引用,通过检查附有该修饰符的变量是否为nil,可以判断被赋值的对象是否已经废弃。
该修饰符和他的名字一样unsafe,是不安全的所有权修饰符。
附有__unsafe_unretained修饰符的变量不属于编译器的内存管理,附有unsafe修饰符的变量同附有weak的修饰符变量一样,因为自己生成并持有的对象不能被自己持有,所以生成的对象会立马释放。
看起来unsafe和weak是一样的,让我们来看看unsafe的源码:
id __unsafe_unretained obj1 = nil;
{
//自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
//因为obj0变量的强引用,所以自己持有对象
obj1 = obj0;
NSLog(@"%@", obj1);
//虽然obj0变量赋值给obj1,但是obj1变量既不持有对象的强引用也不持有弱引用
}
//因为obj0变量超出了其作用域,强引用失效,所以自动释放自己持有的对象,因为对象无持有者,所以对象被废弃
NSLog(@"%@", obj1);
我们看一下结果:
按理说此时的对象已经被废弃了,obj1(悬垂指针)无法访问,但还是访问到了对象,程序只有在极个别情况下才会崩溃,这就是为什么不安全。
不过现在这个修饰符基本用不到了。
ARC有效时autorelease会怎么样呢?原则上不能使用autorelease,也不能使用NSAutoreleasePool类,但实际上他是起作用的。
//在ARC无效时
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
//ARC有效时
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
指定@autoreleasepool块代替”NSAutoreleasePool类对象生成、持有以及废弃"
附有__autoreleasing修饰符的变量替代autorelease方法,即对象被注册到autoreleasePool。
但是,显示使用该修饰符是非常罕见的。
当变量取得非自己生成并持有的对象时,虽然可以使用alloc/new/copy/mutablecopy以外的方法来获取对象,但对象已经被注册到了autoreleasepool。
这是因为编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool中
id __strong obj1 = [NSMutableArray array];
// obj为强引用,自己持有对象
// 编译器判断方法后自动注册到autoreleasepool
下面是取得非自己生成并持有的对象源代码
+ (id) array {
id obj = [[NSMutable alloc] init];
return obj;
}
由于return
使得对象的变量超出作用域,所以强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器会自动将其注册。
虽然__weak修饰符实为了避免循环引用而使用的,但在访问附有weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。
id __weak obj1 = obj0;
//在源码中
id __weak obj1 = obj0;
id __autoreleaseing tmp = obj1;
为什么访问附有__weak修饰符的变量时必须访问注册到aotoreleasepool的对象呢?
这是因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果要把访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束前都能确保该对象存在。
同前面讲述的id obj和id __strong obj 完全一样,那么id指针id *obj可以由id __strong *obj的例子推出吗?其实,推出来的是id __autoreleasing *obj
同样,对象的指针NSObject **obj便成为了NSObject *__autoreleasing *obj
作为alloc/new/copy/mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。使用附有__autoreleasing修饰符的变量作为对象取得参数,与除去alloc/new/copy/mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。
赋给对象指针时,所有权修饰符必须一致。
@autoreleasepool
在ARC无效时,可以将NSAutoreleasePool对象嵌套使用,同样的,@autoreleasepool块也能够嵌套使用
推荐不管ARC是否有效,都可以使用@autoreleasepool块
不能使用retain/release/retainCount/autorelease
不能使用NSAllocateObject/NSDeallocateObject
须遵守内存管理的方法命名规则
不要显示调用dealloc
使用@autoreleasepool块代替NSAutoreleasePool类
不能使用区域(NSZone)
对象型变量不能作为c语言结构体成员
显示转换id和void *。