我是前言
这次探索源自于自己一直以来对ARC
的一个疑问,在MRC
时代,经常写下面的代码:
- (void)dealloc { |
对象析构时将内部其他对象release
掉,申请的非Objc对象的内存当然也一并处理掉,最后调用super
,继续将父类对象做析构。而现如今到了ARC
时代,只剩下了下面的代码:
- (void)dealloc |
问题来了:
- 这个对象实例变量(Ivars)的释放去哪儿了?
- 没有显示的调用
[super dealloc]
,上层的析构去哪儿了?
ARC文档中对dealloc过程的解释
llvm官方的ARC文档中对ARC下的dealloc过程做了简单说明,从中还是能找出些有用的信息:
- 大概意思是:dealloc方法在最后一次release后被调用,但此时实例变量(Ivars)并未释放,父类的dealloc的方法将在子类dealloc方法返回后自动调用
- 理解:ARC下对象的实例变量在根类[NSObject dealloc]中释放(通常root class都是NSObject),变量释放顺序各种不确定(一个类内的不确定,子类和父类间也不确定,也就是说不用care释放顺序)
所以,不用主调[super dealloc]
是因为自动调了,后面再说如何实现的;ARC下实例变量在根类NSObject析构时析构,下面就探究下。
NSObject的析构过程
通过apple的runtime源码,不难发现NSObject执行dealloc
时调用_objc_rootDealloc
继而调用object_dispose
随后调用objc_destructInstance
方法,前几步都是条件判断和简单的跳转,最后的这个函数如下:
void *objc_destructInstance(id obj) |
简单明确的干了三件事:
- 执行一个叫
object_cxxDestruct
的东西干了点什么事 - 执行
_object_remove_assocations
去除和这个对象assocate的对象(常用于category中添加带变量的属性,这也是为什么 (Edit: 在ARC或MRC下都不需要remove,感谢@sagles的基情提示) - 执行
objc_clear_deallocating
,清空引用计数表并清除弱引用表,将所有weak
引用指nil(这也就是weak变量能安全置空的所在)
所以,所探寻的ARC自动释放实例变量的地方就在cxxDestruct
这个东西里面没跑了。
探寻隐藏的.cxx_destruct
上面找到的名为object_cxxDestruct
的方法最终成为下面的调用:
static void object_cxxDestructFromClass(id obj, Class cls) |
代码也不难理解,沿着继承链逐层向上搜寻SEL_cxx_destruct
这个selector,找到函数实现(void (*)(id)
(函数指针)并执行。
搜索这个selector的声明,发现是名为.cxx_destruct
的方法,以点开头的名字,我想和unix的文件一样,是有隐藏属性的
从这篇文章中:
和《Effective Objective-C 2.0》中提到的:
可以了解到,.cxx_destruct
方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作
通过实验找出.cxx_destruct
最好的办法还是写个测试代码把这个隐藏的方法找出来,其实在runtime中运行已经没什么隐藏可言了,简单的类结构如下:
@interface Father : NSObject |
只有两个简单的属性,找个地方写简单的测试代码:
// start |
主要目的是为了让这个对象走dealloc方法,新建的son对象过了大括号作用域就会释放了,所以在after new
这行son对象初始化完成,在gone
这行son对象被dealloc
个人一直喜欢使用NSObject+DLIntrospection这个扩展作为调试工具,可以轻松打出一个类的方法,变量等等。
将这个扩展引入工程内,在after new
处设置一个断点,run,trigger后使用lldb命令用这个扩展输出Son类所有的方法名:
发现了这个.cxx_destruct
方法,经过几次试验,发现:
- 只有在ARC下这个方法才会出现(试验代码的情况下)
- 只有当前类拥有实例变量时(不论是不是用property)这个方法才会出现,且父类的实例变量不会导致子类拥有这个方法
- 出现这个方法和变量是否被赋值,赋值成什么没有关系
使用watchpoint定位内存释放时刻
依然在after new
断点处,输入lldb命令:
watchpoint set variable son->_name |
将name
的变量加入watchpoint,当这个变量被修改时会触发trigger:
从中可以看出,在这个时刻,_name
从0x00006b98变成了0x0,也就是nil,赶紧看下调用栈:
发现果然跟到了.cxx_destruct
方法,而且是在objc_storeStrong
的过程中释放
刨根问底.cxx_destruct
知道了ARC下对象实例变量的释放过程在.cxx_destruct
内完成,但这个函数内部发生了什么,是如何调用objc_storeStrong
释放变量的呢?
从上面的探究中知道,.cxx_destruct
是编译器生成的代码,那它很可能在clang前端编译时完成,这让我联想到clang的Code Generation
,因为之前曾经使用clang -rewrite-objc xxx.m
时查看过官方文档留下了些印象,于是google:
.cxx_destruct site:clang.llvm.org |
结果发现clang的doxygen
文档中CodeGenModule
模块正是这部分的实现代码,cxx相关的代码生成部分源码在
http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html
位于1827行,删减掉离题部分如下:
/// EmitObjCIvarInitializations - Emit information for ivar initialization |
这个函数大概作用是:获取.cxx_destruct
的selector,创建Method,并加入到这个Class的方法列表中,最后一行的调用才是真的创建这个方法的实现。这个方法位于
http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html
1354行,包含了构造和析构的cxx方法,继续跟随.cxx_destruct
,最终调用emitCXXDestructMethod
函数,代码如下:
static void emitCXXDestructMethod(CodeGenFunction &CGF, ObjCImplementationDecl *impl) |
分析这段代码以及其中调用后发现:它遍历当前对象所有的实例变量(Ivars),调用objc_storeStrong
,从clang
的ARC文档上可以找到objc_storeStrong
的示意代码实现如下:
id objc_storeStrong(id *object, id value) { |
在.cxx_destruct
进行形如objc_storeStrong(&ivar, null)
的调用后,这个实例变量就被release
和设置成nil
了
注:真实的实现可以参考 http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 2078行
自动调用[super dealloc]的实现
按照上面的思路,自动调用[super dealloc]
也一定是CodeGen
干的工作了
位于 http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 492行StartObjCMethod
方法中:
if (ident->isStr("dealloc")) |
上面代码可以得知在调用dealloc
方法时被插入了代码,由FinishARCDealloc
结构定义:
struct FinishARCDealloc : EHScopeStack::Cleanup { |
上面代码基本上就是向父类转发dealloc
的调用,实现了自动调用[super dealloc]
方法。
总结
- ARC下对象的成员变量于编译器插入的
.cxx_desctruct
方法自动释放 - ARC下
[super dealloc]
方法也由编译器自动插入 - 所谓
编译器插入代码
过程需要进一步了解,还不清楚其运作方式 - clang的
CodeGen
也值得深入研究一下
References:
- http://clang.llvm.org/docs/AutomaticReferenceCounting.html
- http://my.safaribooksonline.com/book/programming/objective-c/9780132908641/3dot-memory-management/ch03
- http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html
原创文章,转载请注明源地址,blog.sunnyxx.com