第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开辟内存空间相关的源码。敬请期待吧!!!
















