在 objective-c 中,内存的引用计数一直是一个让人比较头疼的问题。尤其是当引用计数涉及到 arc、blocks 等等的时候。似乎 ARC 的出现只是让我们解放了双手,由于底层实现依然依赖引用计数,所以开启 ARC 后,只有对引用计数机制更加了解,才能避免 Cycle Retain、Crash 等问题的出现。
但是由于使用 ARC 可以显著提高编码效率,所以建议尽量启用 arc,本文内容也将以arc 为主,所有测试等如未说明均表示开启 arc。
oc 中内存的管理主要依赖引用计数,而对引用计数的影响又依赖修饰属性(暂且这么称呼),oc 中常用的修饰属性如下:
属性
(1)修饰属性(使用@property 定义时)
读写控制:
readwrite:可读可写,会生成 getter 和 setter 方法。readonly:只读,只会生成 getter 方法,不会生成 setter 方法。
引用方式:
copy:拷贝,复制一个对象并创建 strong 关联,引用计数为 1 ,原来对象计数不
变。
assign:赋值,不涉及引用计数的变化,弱引用。ARC 中对象不能使用 assign,但
原始类型(BOOL、int、float)仍然可以使用。
retain:持有,对原对象引用计数加 1,强引用。ARC 中使用 strong。weak:赋值(ARC),比 assign 多了一个功能,对象释放后把指针置为 nil,避免了
野指针。
strong:持有(ARC),等同于 retain。
线程安全:
nonatomic:非原子操作,不加同步,多线程访问可提高性能,但不是线程安全的。
atomic:原子操作,与nonatomic相反。
(2)修饰变量(修饰不使用@property 定义时,比如函数内的局部变量)
__strong:是缺省的关键词,强引用。
__weak:声明了一个可以自动置nil的弱引用(ARC中)。
__unsafe_unretained:声明一个弱引用,但是不会自动nil化(只有iOS4才应该使用)。
__autoreleasing:用来修饰一个函数的参数,这个参数会在函数返回的时候自动释放(类似autorelease)。
(2)默认的引用计数:
alloc:分配对象,分配后引用计数为1。
autorelease:对象引用计数减1,但如果为0不马上释放,等待最近一个pool时释放。
使用ARC
ARC,全称叫Automatic Reference Counting,该机制从iOS5开始导入。简单地说,就是代码中自动加入了retain/release。所以,其底层机制还是引用计数,所以掌握引用计数对内存管理依旧非常非常重要,我甚至觉得使用arc的前提就是充分了解引用计数机制,否则几乎每天都要Cycle retain,Crash做斗争。
在你打开ARC时,你是不能使用retain release autorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak和strong,其中strong就相当于retain属性,而weak详单与assign,基础类型还是使用assign。
(1)strong还是weak:
说到底就是一个归属权的问题。小心出现循环引用导致内存无法释放,或者需要引用的对象过早被释放。大体上:IBOutlet 可以为weak,NSString 为copy 或 strong,Delegate 一般为 weak,基础类型用assign,不过要注意具体使用情况。
(2)outlet使用strong还是weak
官方文档建议一般outlet属性都推荐使用weak,不是直接作为mainView里面一个subview直接显示出来,而是需要通过实例化创建出来的view,应该使用strong(自己创建的自己当然要保持引用了)。但是要注意使用weak时不要丢失对象的所有权,否则应该使用strong。
(3)delegate使用strong还是weak
delegate主要涉及到互相引用和crash(引用被释放)问题,为了方式这两个问题发生,delegate一般使用weak。
先看代码:
//MyClassDelegate协议
@protocol MyClassDelegate <NSObject>
- (void) myClassOnSomeEvent:(MyClass*)myClass;
@end
//MyClass类
@interface MyClass
@property (weak,nonatomic) id<MyClassDelegate> delegate; // (1)这里使用weak
@end
@interface myViewController
// myViewController中创建一个MyClass
@property (strong,nonatomic) MyClass *myClass;
@end
@implementation myViewController
- (void)someAction
{
myClass = [[MyClass alloc] init];//(2)
myClass.delegate = self;//(3)
....
}
@end
在myViewController中,执行myClass = [[MyClass alloc] init];//(2),此时myViewController将会持有一个MyClass的引用.执行myClass.delegate = self;//(3)时,myClass也会引用myViewController.
1.当myClass.delegate使用weak时,不会出现互相引用问题,也不会出现crash(引用被释放问题)
引用关系如下(假设myViewController由SomeBody持有:)
上图引用关系分析:
第一行:
Somebody创建(持有)myViewController,所以myViewController的引用计数为1。.
myViewController持有myClass,所以myClass的引用计数为1.
myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响.
第二行:
如果Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewcontroller自己将会被释放(引用计数是0),并减少所持有对象(myClass)的引用计数.
第三行:
myClass引用计数为0,被释放,由于是delegate 是weak属性的,所以delegate将自动被设置为空.
2.当myClass.delegate 使用strong时,会出现相互引用问题,导致对象无法被释放.
引用关系如下:
第一行:
Somebody创建(持有)myViewController,所以myViewController的引用计数为1.
myViewController持有myClass,所以myClass的引用计数为1.
myClass通过成员delegate引用myViewController,但使用strong引用,所以引用计数加1,变成2.
第二行:
如果Somebody释放myViewController,则myViewController引用计数减1,变成1,此时myViewController不会被释放,myClass引用计数为1,也不会被释放,这样就造成了互相引用问题.
而且没有外部对象在引用myViewController或myClass,造成了myViewController和myClass无法被释放.
3.当myClass.delegate 使用weak时,如果有另一个Somebody同时持有了myClass,由于weak可以自动置nil,所以不会出现crash(引用被释放)问题
则引用关系如下:
第一行:
Somebody创建(持有)myViewController,所以myViewController的引用计数为1.
myViewController持有myClass,所以myClass的引用计数为1.
myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响.
第二行:
新的Somebody持有同一个myClass,导致myClass引用计数加1,变成2.
第三行:
如果Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewController自己将会被释放(引用计数是0),并减少所持有对象(myClass)的引用计数.使myClass引用计数减1,变成1.由于myClass引用myViewController的delegate是weak属性的,所以delegate将自动被设置为空,不会出现crash(引用被释放)问题.
△注意:如果myClass引用myViewController的delegate是assign的话,则delegate不会被自动设置为空,将导致delegate再次调用myViewController时出错(myViewController已经被释放了).
第四行:
Somebody正常持有myClass,如果此时Somebody释放myClass,则myClass引用计数减1并释放,不会出现任何问题.
关于block和引用计数
(1)修饰block
如果需要block在它被声明的作用域被销毁后继续使用的话,你就需要做一份拷贝,拷贝会把block移到堆里面.所以,使用@property时设置通常如下:
@property(copy, nonatomic)void(^block)(void);
(2)retain cycle的问题
block在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后再block块内使用该只读拷贝.所以在使用block过程中,经常会遇到retain cycle的问题,例如:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (void)loadView {
[super loadView];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note){
[sself dismissModalViewControllerAnimated:YES];
}];
}
在block中用到了self,self会被block retain,而_observer会copy一份该block,就是说_observer间接持有self,同时当前的self也会retain _observer,最终导致self持有_observer,_observer持有self,形成retain cycle.
对于在block中的retain cycle,在2011 WWDC Session #322 (Objective-C Advancements in Depth)有一个解决方案weak-strong dance,其实现如下:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (void)loadView {
[super loadView];
__weak TestViewController *wself = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
TestViewController *sself = wself;
[sself dismissModalViewControllerAnimated:YES];
}];
}
在block中使用self之前先用一个__weak变量引用self,导致block不会retain self,打破retain cycle,然后再block中是用wself之前先用__strong类型变量引用wself,以确保使用过程中不会dealloc.简而言之就是推迟对self的retain,在使用时才进行retain.
(3)return一个block
返回一个block时,ARC会自动将block加上autorelease,所以需要注意,如果执行过程中不能接受在runLoop接受后才释放block,就需要自己加入@autoreleasepool块,但是测试发现64位iOS/Mac时,系统会自动在使用结束后立即释放,32位则要等到runLoop结束.
- (void)test
{
//@autoreleasepool{
AutoTest *a = [AutoTest sAutoTest];
NSLog(@"1");
a = nil;
NSLog(@"2");
a = [[AutoTest alloc] init]; //}
NSLog(@"3");
}
- (IBAction)ok:(id)sender
{
[self test];
NSLog(@"4");
}
//执行结果
1释放 23释放4 64位
123释放4 32位
12释放 3释放4 32位+@autoreleasepool
(3)block作为参数
block作为参数时,如果使用范围超过了block的作用域(比如异步时,或者将block传递给其他对象等等),则需要copy此block,copy建议在使用此block的方法内实现(谁使用,谁管理),而不是在传递参数时copy.注意,block过一个strong类型的指针,会自动copy.经过copy过的block会从栈空间移动到堆上,并且copy一个已经在堆上的block时,此block不会受影响.
*如果想了解block的内存管理原则,建议查阅相关文档,了解一下block的内部实现方式.block的内存管理很难用简单的几句话来描述.
Somebody创建(持有)myViewController,所以myViewController的引用计数为1.