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