什么是自动引用计数
顾名思义,自动引用计数(ARC)是指内存管理中对引用采取自动计数的技术。我们知道以前的程序员关于内存管理是手动管理的也就是MRC,在后来才引入了ARC。那么关于ARC相比于MRC最大的优化是什么呢?总结一下就是:在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或者是release代码这样就会在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
内存管理/引用计数
内存管理的四个法则
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不再需要自己持有的对象时释放。
- 非自己持有的对象无法释放。
对象操作与OC方法的对应:
__strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。 以下源代码中的id变量,实际上被附加了所有权修饰符:
id obj = [[NSObject alloc] init];
id和对象类型在没有明确指定所有权修饰符时,默认为__strong修饰符。上面的源代码与以下相同:
id __strong obj = [[NSObject alloc] init];
附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会是放弃被赋予的对象。__strong修饰符便是对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
__strong修饰符的变量之间相互赋值
id __strong obj0 = [[NSObject alloc] init];//生成对象A
id __strong obj1 = [[NSObject alloc] init];//生成对象B
id __strong obj2 = nil;
obj0 = obj1;//obj0强引用对象B;而对象A不再被ojb0引用,被废弃
obj2 = obj0;//obj2强引用对象B(现在obj0,ojb1,obj2都强引用对象B)
obj1 = nil;//obj1不再强引用对象B
obj0 = nil;//obj0不再强引用对象B
obj2 = nil;//obj2不再强引用对象B,不再有任何强引用引用对象B,对象B被废弃
通过上面的代码我们可以发现,__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。赋值的本质是强引用的转变
关于__strong导致的循环引用的问题
举个例子:
Test类中有一个强引用类型的成员变量obj_,同时设置其set方法
@interface Test : NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
#import "Test.h"
@implementation Test
- (id)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
id test0 = [[Test alloc] init];//生成TestA
id test1 = [[Test alloc] init];//生成TestB
[test0 setObject:test1];
[test1 setObject:test0];
//NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)test0));
}
return 0;
}
可以看到我们声明了两个对象test0和test1,同时其内部含有自己的obj_,由于set方法同时给两个对象的成员变量分别赋值另一个对象所持有的TestA和TestB对象,所以现在的情况就是:
test0持有TestA
test1.obj也持有TestA
test1持有TestB
test0.obj也持有TestB
失效阶段的表示
testA 不能dealloc 因为test1.obj还持有testA
想要废除test1.obj就是要遵行成员变量的生命周期是与对象同步的这个观点,所以我们需要废除testB
而对于testB来说也是这样,想废除test0.obj就是要废除testA
因为都是强引用,所以就造成了谁也不愿意让步,所以造成了循环
循环引用有什么隐患?循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象超出其生存周期后继续存在。
__weak修饰符
我们刚刚提到了使用__strong修饰符会造成循环引用的问题,那么如何才能避免循环引用呢?有__strong就说明肯定有__weak,也许你不用知道OC为什么会引入__weak修饰符,但你要知道使用__weak修饰符可以避免循环引用。
__strong修饰符是强引用,那么__weak修饰符就提供了弱引用。弱引用是什么意思呢,你可以理解为它因为弱所以不能持有对象的实例
我们看一下下面的代码:
id __weak obj = [[NSObject alloc] init];
我们编译一下就会发现,编译器会发出警告:
这是编译器在告诉你,此源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj。即变量obj持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。那么如何才能使编译器不发出警告呢,我们将代码改成以下的形式:
id __strong obj0 = [[NSObject alloc] init];
id __weak obj1 = obj0;
因为带__weak修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前可能发生循环引用的类成员变量改成 附有__weak修饰符的成员变量的话,就可以避免循环引用。
@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id__strong)obj;
@end
__weak修饰符还有另一优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。
通过检查附有weak修饰符的变量是否为nil,可以判断被赋值对象是否已废弃。
遗憾的是,weak修饰符只能用于iOS5以上及OS X Lion以上版本的应用程序。在iOS4以及OS X Snow Leopard的应用程序中可使用__unsafe _unretained修饰符来代替。
__unsafe _unretained修饰符
__unsafe _unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有__unsafe _unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时要注意。
weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为 nil。 _Unsafe_Unretain 不会置为 nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain 比 __weak 效率高。
悬垂指针 指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针。野指针,没有进行初始化的指针,其实都是 野指针
附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,生成的对象会立即释放。
在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在,如果不存在,那么程序就会崩溃。
__autoreleasing修饰符
在上一节内容iOS开发——MRC(手动内存管理)我解释了关于MRC中的autorelease和NSAutoreleasePool等知识,这些也是MRC情况下的方法,但是在实际情况下,ARC有效时autorelease功能也是起作用的,如图所示:
__autoreleasing修饰符自动调用
编译器在编译时会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。
以下情况即使不使用__autoreleaseing修饰符也能使对象注册到autoreleasepool。
+ (id) array {
return [[NSMutableArray alloc]init];
}
如下:
+ (id) array {
id obj = [[NSMutableArray alloc]init];
return obj;
}
以下为使用__weak修饰符的例子。虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj class]);
以下源代码与此相同。
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@", [tmp class]);
那么为什么访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象呢?
这是因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此,访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象。
最后一个可非显示地使用__autoreleasing修饰符的例子,同前面讲述的id obj和id __storng obj完全一样。那么id的指针id *obj又如何呢?可以由id __strong obj的例子类推出id __strong *obj吗?其实,推出来的是id __autoreleasing *obj。同样地,对象的指针NSObject **obj便成为了NSObject *__autoreleasing *obj。
像这样,id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
ARC规则
- 在ARC环境下编译源代码,必须要遵守一定的规则。具体ARC规则如下:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必须遵守内存管理的方法名规则
- 不要显式调用dealloc
- 使用@autorelease块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体的成员
- 显式转换id和void*
不能使用 retain/release/retainCount/autorelease
设置ARC有效时,无需再次键入retain或release代码
前面我们介绍了retain/release/retainCount/autorelease这些方法是MRC里使用的,在我们设置ARC有效时,再使用的话编译器就会报错,所以总结一下:设置ARC有效时,禁止再次键入retain或者是release代码。
不能使用NSAllocateObject/NSDeallocateObject
一般情况下,通过调用NSObject类的alloc类方法来生成并持有OC对象:
id obj = [NSObject alloc];
但是就如GNUstep的alloc实现所示,实际上是通过直接点用NSAllocateObject函数来生成并持有对象的。
在ARC有效时,禁止使用NSAllocateObject函数。同retain等方法一样,如果使用会报错,所以禁止使用。
须遵守内存管理的方法命名规则
在ARC无效时,用于对象生成/持有的方法必须遵守以下的命名规则。
- alloc
- new
- copy
- mutableCopy
关于init
以init开始的方法的规则要比alloc/new/copy/mutableCopy更严格。该方法必须是实例方法,并且必须要返回对象。返回的对象应为id类型或该方法声明类的对象类型,返回的对象并不注册到autoreleasepool上。基本桑拿只是对alloc方法返回值的对象进行初始化处理并返回该对象。
不能显示调用dealloc
dealloc无法释放不属于该对象的一些东西,需要我们重写时加上去,例如:
- 通知的观察者,或KVO的观察者
- 对象强委托/引用的解除(例如XMPPMannerger的delegateQueue)
- 做一些其他的注销之类的操作(关闭程序运行期间没有关闭的资源)
__bridge
属性
属性关键字与所有权修饰符
ARC的实现
__strong修饰符
表示引用为强引用。在ARC模式下,只要一个变量有__strong标识了,就标识拥有了赋值的对象,不管赋值的对象是怎么来的。也可理解为只要见到__strong标识,编译器就会给那些不是你持有的对象自动加上retain,并且在变量超出作用域后自动调用了一次release。
那么赋值给附有__strong修饰符的变量在实际的程序中到底是怎样运行的呢?
//编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
如原源代码所示,两次调用objc_msgSend方法(alloc方法和init方法),变量作用域结束时通过objc_release释放对象。虽然ARC不能使用release方法,但由此可知编译器自动插入了release。
像该源代码这样,返回注册到 autoreleasepool 中对象的方法使用了objc_autoreleaseReturnValue 函数返回注册到autoreleasepool中的对象。但是 objc_autoreleaseReturnValue函数同 objc_autorelease 函数不同般不仅限于注册对象到autoreleasepool中。
objc_autoreleaseReturnValue 两数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调国方在调用了方法或函数后紧接着调用 objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue 函数与 objc_retain 函数不同,它即便不注册到 autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue 函数和objc_retainAutoreleasedReturnValue 函数的协作,可以不将对象注册到autoreleasepool中而直按传递,这一过程达到了最优化。如图所示: