内存管理-dealloc方法到底应该怎么写?
前言
使用ARC已经很长时间了,基本已经快忘却了retain、release、dealloc等方法了,但即便使用ARC,对于一些内存的处理我们依然需要手动进行。比如dealloc方法,当我们重载dealloc方法一样会被调用,只是不能调用其父类的方法[super dealloc],在dealloc方法中通常需要做的有移除通知或监听操作,或对于一些非Objective-C对象也需要手动清空,比如CoreFoundation中的对象。再而ARC的内存销毁具有一定的滞后性,也可将一些变量手动置空,也就是告诉系统这些变量已经使用完毕可以释放了,但是对于变量置空一直有这样的疑惑,下面两种写法到底有什么不同?
- (void)dealloc {
self.name = nil;
}
- (void)dealloc {
_name = nil;
}
翻阅了苹果的官方文档Don’t Use Accessor Methods in Initializer Methods and dealloc,但其只是简单的说明不要在init与dealloc方法中使用读写方法进行初始化设置,并未详细的说明其原因,遂查阅了网上的一些问答,在这里加以总结,该文由此而来。
正文
首先认知如上两种写法的区别,self.name意为调用属性name的setter方法进行赋值
- (void)setName:(NSString*)name {
_name = name;
}
即将nil作为参数传递到setName:方法中,其方法内部本身执行的仍为_name = nil,乍看来和手动在dealloc中书写_name = nil没有什么区别,但是setter方法并没有看起那样简单,若在MRC中通常setter方法如下所示:
- (void)setName:(NSString*)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
对于setter赋值方法采用的为释放旧值保留新值的方式。直接调用_name=nil避免了指针转移问题且避免了Objcetive-C的“方法派发”操作,因此直接调用_name=nil相比self.name=nil执行效率会高一些。
参考自:《Effective Objective-C 2.0》第7条:在对象内部尽量直接访问实例变量。
难道仅仅是提升了一些肉眼不能察觉的执行效率提升问题吗?肯定不是的,在某些情况下如果使用self.name=nil这种写法会使程序造成错误甚至崩溃。
假设在父类的dealloc方法中使用读写方法(self.name=nil)进行置空操作,如果子类重写了其读写方法,当所创建的子类对象销毁时进而调用父类的dealloc方法,就会造成访问已释放对象的情况,从而发生崩溃,口述不明显,代码如下:
父类
#import <Foundation/Foundation.h>
@interface BaseClass : NSObject
@property(nonatomic) NSString *baseObj;
@end
#import "BaseClass.h"
@implementation BaseClass
- (void)dealloc {
self.baseObj = nil;//读写方法置空
}
@end
子类
#import "BaseClass.h"
@interface SubClass : BaseClass
@property (nonatomic) NSString *subObj;
@end
#import "SubClass.h"
@implementation SubClass
- (void)setBaseObj:(NSString *)baseObj {
NSLog(@"%@",[NSString stringWithString:_subObj]);
}
- (void)dealloc {
_subObj = nil;
}
例中在BaseClass中声明属性baseObj,子类SubClass继承于BaseClass并重写其属性baseObj的setter方法,并在此打印了本类的属性self.subObj。
在主函数中运行如下代码会出现什么情况?
- (void)viewDidLoad {
[super viewDidLoad];
SubClass *class = [[SubClass alloc]init];
class.subObj = @"子类属性";
}
没错,程序崩溃了,那我们分析下为什么会产生崩溃情况。我们可以分别在父类与子类的dealloc方法和已经重写的setBaseObj方法中添加断点查看运行流程。
执行顺序为
![](https://img.haomeiwen.com/i1767950/9112771904522f8a.png)
![](http://upload-images.jianshu.io/upload_images/1767950-2d89c8b1d91ee335.png)
![](http://upload-images.jianshu.io/upload_images/1767950-78614bc8e6dc251f.png)
简单分析下,当SubClass所创建的对象使用完毕销毁会调用其本身dealloc方法,接下来dealloc内部调用[super dealloc]从而执行父类dealloc方法,但因为父类dealloc方法是使用self.baseObj=nil这种读写方法进行置空,因此接下来调用子类重写的setBaseObj方法,但此时子类已经销毁,访问并使用了一个已经不存在的对象,从而发生崩溃。
因此说明在dealloc中使用读取方法置空是不安全的,轻则获取null,重则程序崩溃。
参考自:不要在init和dealloc函数中使用accessor
最后我们在回想下MRC中dealloc的写法
- (void)dealloc {
[_name release];
_name = nil;
}
在MRC下应该使用[_name release]还是[self.name release],或者在ARC下系统又是如何处理的呢?
当然是采用[_name release]而非[self.name release],因为我们调用的getter方法本质是这样的形式:
- (NSString*)name {
return [[_name retain] autorelease];
}
可以看出[self.name release]添加了额外的retain与autorelease操作,释放时机无法控制,可能会导致重复释放导致程序崩溃,而通过[_name release]可以直接将该变量的内存进行释放,避免崩溃或悬空指针,因此还是[_name release]会更好些。
参考自:Why shouldn't I use the getter to release a property in objective-c?
该文对总结的问答进行整理以加深印象,如有其他见解欢迎评论指出。