[TOC]
复杂度高的项目系统,我惯于花时间梳理出大手稿,作为参考,根据这个思路,我花时间对于OC底层的机制进行梳理,产出大手稿若干。
第三篇:对象销毁流程
未完成!NFY Not finished yet.
基本的流程已经梳理完了,图显得有些突兀,主要是因为右上角的那个方法中有很多官方的注释,以及我没有对其进行拆解和简化。
这里面有一些知识点没有特别细致的展开,例如:
- sidetable 结构和应用操作的整理汇总
- weak 散列表的操作展开讲
- 最后一些流程用到的方法查找函数是仅仅提供给查找
构造函数
和析构函数
使用的
0. 前言
最近面了一些试,某位非常有水平的面试官问了我一个有意思的问题:
delloc 的时候增加引用计数,会防止销毁吗?
当时猜测了一下,没答很全面,好好梳理一下 delloc 的流程。
1 | 1. 调用Dealloc之前的流程 |
穿插研究复习几个问题,提供几个真正属于 runtime 的面试题:
1 | 1. customRR是什么? |
1. 调用Dealloc之前的流程
首先创造一个简单对象的 delloc 流程下符号断点 delloc :
1 | { |
可以捕获断点 bt
出来:
1 | <Fruit: 0x100658430> (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.2 |
可以看到进入的流程:objc_storeStrong
–> _objc_release
–> release
……这就够了,最新的代码走起:
1 | // PS: 如果不喜欢 bt 这个词的话,可以直接看: |
调用了 NSObject.mm
的 objc_storeStrong()
方法,传入的参数是 对象的地址以及 obj 为 nil,其实就是一个赋空值的过程。
1 | void |
很明显这个 objc_retain(nil);
没什么作用,重点就是 objc_release(banana);
objc_release() 方法的实现很简单,第一判空返回,第二判TaggedPointer返回,第三调用obj->release()
。
1 | __attribute__((aligned(16), flatten, noinline)) |
继续调用 objc_object::release()
1 | // 等同于调用 [this release], 如果没有重写的话,就走一个捷径 |
插入去研究了 什么是 customRR 和 customCore:
1 | //class 或 superclass 实现了默认的 retain/release/autorelease/retainCount/ _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法 |
也就是说,👆这里 fastpath 大多数情况会调用 rootRelease,如果你的类自己实现了RR方法,就给 msgSend release 方法。
继续跟踪 rootRelease()
1 | ALWAYS_INLINE bool |
rootRelease(true, false);
代码太多,看图里有详情,这里解释一下做的事情:
- 判 Tagged Pointer 返 false
- sideTable 和 extra_rc 的引用计数合并,如果都没有了,就通过 msgSend dealloc
另外这里提到:sidetable 使用的锁是自旋
问题1:Tagged Pointer 为啥这个时候返回False
TP不参与任何的引用计数,引用本身即是值,因为他很单纯,指针混淆、身体里大部分都是值。讲TP文章太多,在此不赘述了。
问题2:为什么会有 sideTable 和 extra_rc?
首先:nonpointerISA 对象的引用计数优先放在isa里面的ExtraRc里面,直到突破了那个区域的限制,就转到 SideTable 中的 两个Map中存储。
其次:而 pointerISA 直接在 SideTable 中的 两个Map中存储。
问题3:开篇的面试题
没有用,会继续释放,因为进入 dealloc 之前,问题2,已经判断了引用计数。
引发了另一个我想到的问题
一个Strong一个weak在delloc里面指向对象自己,会怎么样?稍后尝试!!!
我这里根据下面的推测猜测一下,strong 可能会有问题,甚至可能野指针,weak的话后面会释放,所以没关系。
2. Dealloc()
通过msgSend进入Dealloc
1 | // NSObject.mm |
1 | // objc-object.h |
好如果你这个对象:是nonpointter的话,请问你是否有弱引用
或者你是否有关联对象
或者是否有析构函数
或者是否你的引用计数太打了,存到sidetable里面去了
。
以上问题,如果你有一项的话,跟我们走一趟,调用下面的object_dispose((id)this)
如果以上你都没有,那好你是一个很单纯的对象,恭喜你,free()
and go!
另外,去执行 object_dispose((id)this)
的对象也不要担心,看下面的实现,只是在 free(obj)
之前多调用了一个 objc_destructInstance(obj)
而已。
1 | /************************ |
objc_destructInstance
这个是本篇的核心方法:
1 | /************************************ |
2.1 析构 CxxDestruct,以及到底是什么!
你看上面的源码说了,先执行析构函数还是先删除关联对象,先析构再移除关联。
object_cxxDestruct(obj)
往下走是 objc-class.mm 这个文件:两个方法,代码就不全粘了,核心就是一个for循环:
1 | // 源:先调用 cls 的析构,再不断调用父类的析构函数 |
看看这个for循环有意思,Cook多骚气,for ( ; cls; cls = cls->superclass)
,然后里面判断了hasCxxDtor
,没有C++Dtor的话人家不写break
,直接return
了。
如果这个for循环找到了C++析构,就调用lookupMethodInClassAndLoadCache,方法寻址,这个方法有注释,仅在构造和析构的时候使用,也做了断言,很安全。
内部实现是一个方法寻址,找到的话填充缓存返回。
关于这个C++的析构函数,这里详细说明下,这里详细释放了对象的每一个实例变量!所以这个方法一点是 LLVM 的 clang 中的 代码生成模块搞出来的。
http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html
它的方法实现核心就是对于这个对象所有实例变量,遍历调用objc_storeStrong()
,然后这个实例变量就解除retain 了。
1 | id objc_storeStrong(id *object, id value) { |
2.2 移除关联对象,复习关联对象实现
看到这里你要知道,我们通过API添加的关联对象,不管你ARC还是MRC,都没必要手动Remove。
objc-references.mm
里面
1 | // 与设置/获取关联的引用不同, |
看删除就能看出来关联对象是如何管理的,这里复习一下:
- 有一个单例
AssociationsManager
- 通过
get()
获取了一个AssociationsHashMap
- 这个
AssociationsHashMap
里面 K:V 是disguise(obj)
:ObjectAssociation
ObjectAssociation
:结构体存储着:policy
,value
3. clearDeallocationg(),处理weak
继续前面的流程,完成2.1 2.2 之后 调用了 objc-object.h
里面的 clearDeallocating
方法:
1 | // objc-object.h |
最后这里才去 side table 里面去处理指针和弱引用问题。
注意 isa.nonpointer 分开两个逻辑。
通过这个对象取出 Side Table
,处理里面的 weakTable
,调用 weak_clear_no_lock(&table.weak_table, (id)this)
和 table.refcnts.erase(it)
完成清除。
将所有weak引用指nil,就是在这里实现的。
最后有一个debug的断言,专门写一个函数来做debug的断言,这个也是非常谨慎的体现,粘出来纪念一下Cook做饭的谨慎:
1 | // NSObject.mm |
4. free(obj)
最后不管对象付不复杂,都会调用 free(obj)
Free 的实现在malloc的源码里面:
5. 父类的 Dealloc 呢?
至今看源码看不到任何调用父类dealloc 的地方,这个网上搜了一下,发现结果在这里!
http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html
clang 在 ARC 的 dealloc 结束的时候插入了如雷的dealloc 的调用。
通过 forward 给 superclass 调用 dealloc
,才实现的[super dealloc]
操作。
这个时候上面有一个隐藏的问题也就解释了,其实 msgSend 来调用 dealloc 方法,会先调用 Fruit 的 dealloc (如果有的话),然后因为ClangCodeGen的插手,才有的调用父类一直到根类的 dealloc。
所有dealloc不需要我们手动写 super dealloc。
References
- OC 看objc源码认识retain、release、dealloc。第一个断点出现之前有一大堆retain release 和 side table 的操作,看不太懂,看了这篇文档
- Objective-C Runtime 中内存释放的并发问题感谢译者 @一只羊的北京,找这个 static int _collecting_in_critical(void),有点点不理解的地方,讲的很透彻,全网中文资料独此一篇
http://zhoulingyu.com/2017/02/15/Advanced-iOS-Study-objc-Memory-2/
iOS进阶——iOS(Objective-C)内存管理·二
- Post link: http://yangzai360.top/2020/11/26/OCBigManuscript03_ObjectDealloc/
- Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.