iOS底层原理03:NSObject的alloc 源码分析
2021-03-20 本文已影响0人
黑白森林无间道
本文主要探索NSObject中的alloc
是与自定义类的alloc
的源码流程
的区别,以及为什么NSObject中的alloc不走源码工程。
NSObject的alloc无法进入源码的问题
- 首先在
objc4-818
可编译源码中的main
函数中增加一个NSObject
定义的对象,NSObject
和HTPerson
同时加上断点
- 在
alloc
的源码实现中加一个断点,同时需要暂时关闭断点
- 运行
target
,断点断在NSObject
部分,打开alloc
源码的断点,然后继续执行,会出现以下这种现象
探索过程
【第一步】探索[NSObject alloc]
走的是哪步源码
接下来,我们就来探索为什么NSObject的alloc会出现这种情况,首先,
- 打开
Debug --> Debug Workflow --> 勾选 Always Show Disassemly
,开启汇编调试
- 关闭源码的断点,只留main中的断点,重新运行程序,然后通过下图的汇编可以发现
NSObject
并没有走alloc
源码,而是走的objc_alloc
- 然后关闭汇编调试,在全局搜索
objc_alloc
,在objc_alloc
中加一个断点,先暂时关闭 - 重新运行进行调试,断住,然后打开
objc_alloc
的断点,发现会进入objc_alloc
的源码实现,此时查看cls
是NSObject
【第二步】探索 NSObject 为什么走 objc_alloc?
首先,我们来看看 NSObject
与 HTPerson
的区别
-
NSObject
是iOS
中的基类,所有自定义的类都需要继承自NSObject -
HTPerson
是继承自NSObject
类的,重写了NSObject
中的alloc
方法
然后根据第一步中汇编的显示,可以看出,NSObject
和 HTPerson
都调用了objc_alloc
,所以这里就有两个疑问
:
- 为什么
NSObject
调用alloc
方法 会走到objc_alloc
源码? - 为什么
HTPerson
中的callAlloc
会走两次?即调用了alloc
,进入源码,然后还要走到objc_alloc
?
HTPerson中callAlloc走两次的原因
- 首先,需要在源码中调试,在
main
中HTPerson
加断点,断在HTPerson
,再在alloc
、objc_alloc
和callAlloc
源码加断点,运行demo
,会断在objc_alloc
源码中(重新运行前需要暂时关闭源码中的所有断点)
- 继续运行,发现
HTPerson
的alloc
会走到objc_alloc --> callAlloc
方法中最下方的objc_msgSend
,表示向系统发送消息
- 继续执行代码,发现会走到
alloc --> callAlloc --> _objc_rootAllocWithZOne
,也就是iOS底层原理02:alloc & init & new 源码分析中的alloc流程.
以下是第二次走到callAlloc
方法中的调用堆栈情况
所以由上述调试过程可以得出,HTPerson
走两次callAlloc
的原因是首先需要去查找sel
,以及对应的imp
的关系,当前需要查找的是 alloc
的方法编号,但是为什么会找到objc_alloc
?这个就需要问系统了,肯定是系统在底层做了一些操作。请接着往下看
NSObject中alloc走到objc_alloc的原因
这部分需要通过 LLVM
源码(即llvm-project
)来分析
准备工作:首先需要一份llvm
源码
- 在llvm源码中搜索
objc_alloc
- 搜索
shouldUseRuntimeFunctionForCombinedAllocInit
,表示版本控制
- 搜索
tryEmitSpecializedAllocInit
,非常著名的特殊消息发送,在这里也没有找到objc_alloc
- 继续尝试,开启上帝视角,通过
alloc
字符串搜索,如果还找不到,还可以通过omf_alloc
:找到tryGenerateSpecializedMessageSend
,表示尝试生成特殊消息发送
- 然后在这个case中可以找到调用alloc,转而调用了
objc_objc
的逻辑,其中的关键代码是EmitObjCAlloc
- 跳转至
EmitObjCAlloc
的定义可以看到alloc
的处理是调用了objc_alloc
由此可以得出 NSObject
中的alloc
会走到 objc_alloc
,其实这部分是由系统级别的消息处理逻辑,所以NSObject的初始化是由系统完成的
,因此也不会走到alloc的源码工程中