第2步:llvm-project 底层分析
由于 llvm-project 项目比较大,这里我们用 VSCode 打开
- 首先,我们全局搜索一下
alloc
或者OMF_alloc:
,来到tryGenerateSpecializedMessageSend
方法,这个方法在 CGObjC.cpp 文件中
21-1.png
我们主要看3号位置的方法解释,这里我翻译了一下,大家可以自行去看,这是苹果对性能的一个优化。主要意思就是:objc
在运行时提供了快捷入口,这些入口比普通的消息发送速度更快,如果运行支持所需要的入口的话,这个方法就会调用并返回结果,否则返回None
,调用者自己生成一个消息发送。
- 知道了
tryGenerateSpecializedMessageSend
的作用,接着我再来看一下tryGenerateSpecializedMessageSend
方法的调用情况,搜索tryGenerateSpecializedMessageSend
,来到GeneratePossiblySpecializedMessageSend
这个方法是运行时在底层的入口,所有的消息发送都会走这里。从代码可以看出,如果tryGenerateSpecializedMessageSend
方法返回None
,这里判断为false
,就会走GenerateMessageSend
方法,也就是调用者自己生成一个普通的msgSend
。
- 然后,我们深入到
tryGenerateSpecializedMessageSend
方法中,看看alloc
是怎么被执行成了objc_alloc
。这里看一下tryGenerateSpecializedMessageSend
方法中4号位置的代码,这里有个条件判断if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc")
,如果成立,就会走EmitObjCAlloc
方法,搜索一下,进去看一下
可以看到EmitObjCAlloc
方法这里生成了一个objc_alloc
的入口(ObjCEntrypoints
),包装为emitObjCValueOperation
被返回执行,并且llvm
对此做一个标记存在Selector
中,而Selector
则记录在SelectorTable
中
由此可以验证:[JQPerson alloc]
在底层会先走到objc_alloc
。
objc_alloc
第一次调用callAlloc
方法,会执行msgSend(cls, @selector(alloc))
(ps:这个第3步callAlloc
中会讲,这里知道一下,先把llvm
这个流程讲完)。
此时llvm
底层还是会走tryGenerateSpecializedMessageSend
,此时,由于已经标记了alloc
的Selector
,不会再走if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc")
这个判断中的代码,最终返回None
。然后由GenerateMessageSend
走普通的消息发送。
第3步:callAlloc
好了,alloc
和objc_alloc
的调用清晰了。接着,我们来看一下最核心的方法callAlloc
。
- 从 objc 源码中我们可以看到
objc_alloc
方法中调用了callAlloc
我们观察一下callAlloc
中的代码,会发现这个方法的最后一行(1937行
)对传入的 cls
做了一次消息发送,发送的消息名称正是alloc
,这似乎可以解释上面走完objc_alloc
方法后,又走到alloc
的现象。但是我们还需要打断点,走一下流程来验证。
- 经断点调试,执行
objc_alloc
后,callAlloc
确实走到了发送alloc
消息这一行,也就是[JQPerson alloc] => objc_alloc => callAlloc
继续走断点,我们会发现执行流程为:[JQPerson alloc] => objc_alloc => callAlloc => alloc => _objc_rootAlloc => callAlloc =>_objc_rootAllocWithZone
- 当前我们已经走完了 main.m 中的
16行
,也就是(JQPerson)p1
的alloc
,此时断点会来到17行
(JQPerson)p2
的alloc
。 - 继续走源码断点,会发现执行流程为:
[JQPerson alloc] => objc_alloc => callAlloc => _objc_rootAllocWithZone
29.png
这里我们就会奇怪,为什么JQPerson
类再次alloc
时,就直接走到if (fastpath(!cls->ISA()->hasCustomAWZ()))
条件判断中的代码了呢?
- 那我们就来看一下
if (fastpath(!cls->ISA()->hasCustomAWZ()))
这句判断到底执行了什么?
进入到if (fastpath(!cls->ISA()->hasCustomAWZ()))
源码中看一下
由以上源码可以看出:
a. 当JQPerson
类第一次调用alloc
方法时,底层会先调用objc_alloc
,此时callAlloc
被第一次调用,callAlloc
内部通过当前cls
的ISA
返回一个Class
对象;
b. 紧接着会去判断当前Class
的cache
中FAST_CACHE_HAS_DEFAULT_AWZ
(存储在元类metaclass
中,记录着cache
中是否已经缓存了alloc/allocWithZone:
方法的实现)这个标志位的值是否为真,由于是第一次执行,没有缓存,所以cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ)
取出来的值是false
,前面加个!
,变成了true
,callAlloc
中if (fastpath(!cls->ISA()->hasCustomAWZ()))
又加了个!
,所以值为false
;
c. 然后走到了if (allocWithZone)
,由于objc_alloc
方法中allocWithZone
参数传值为false
,所以走到了(objc_msgSend)(cls, @selector(alloc))
。然后,callAlloc
被第二次调用,由于执行过了alloc
方法,所以此时有了alloc
的方法缓存,所以if (fastpath(!cls->ISA()->hasCustomAWZ()))
判断为true
,执行_objc_rootAllocWithZone
。
d. 最后就是 main.m 中第17行
,JQPerson
类第二次调用alloc
方法,此时由于JQPerson
类的cache
中已经有了缓存,FAST_CACHE_HAS_DEFAULT_AWZ
这个标志位的值为真,也就是if (fastpath(!cls->ISA()->hasCustomAWZ()))
这个条件为真,所以,会直接执行_objc_rootAllocWithZone
。
下面我画一下流程图,帮助小伙们理解一下:
[JQPerson alloc]流程图新.png
另外,这里我附一张NSObject alloc]
的流程图,有兴趣的小伙们可以去试一试:
这里NSObject alloc]
只走了一遍callAlloc
方法,猜测原因是:系统对 NSObject
做了优化,提前给cache
添加了缓存。
好了,alloc
的底层探索今天先写到这里。下面一篇文章我们将探索一下alloc
开辟内存空间相关的源码。敬请期待吧!!!