前言
上文分析了alloc方法的流程,得出结论,alloc方法的调用流程为 alloc
->_objc_rootAlloc
->callAlloc
->_objc_rootAllocWithZone
->_class_createInstanceFromZone
,最后在_class_createInstanceFromZone
方法计算所需内存空间,开辟内存空间以及关联isa。本以为以及搞明白了alloc的流程。但是当在alloc
方法打上断点,观察函数调用栈发现如下图
我们在XQPerson* person = [XQPerson alloc];
处打上断点,然后查看汇编代码,也能够看到,显示为调用的objc_alloc
如函数调用栈中所示,在调用alloc
方法还调用了objc_alloc
和callAlloc
方法,所以完整的alloc流程应该是 objc_alloc
->callAlloc
->alloc
->_objc_rootAlloc
->callAlloc
->_objc_rootAllocWithZone
->_class_createInstanceFromZone
看到这里,不禁让人产生一个疑问,objc_alloc
和callAlloc
是如何出现在 alloc
的调用流程里面的呢?带着这样的疑问,开始今天的探索。
首先需要确定,objc_alloc
方法是在什么时候调用的,通过函数调用栈或者查看汇编代码都没鞥找到线索,只能在源码里全局搜索objc_alloc
,最终在objc-runtime-new.mm
文件发现了fixupMessageRef
方法,将alloc
的IMP替换成objc_alloc
接下来查找函数调用顺序,找出在什么情况下会进入替换alloc的IMP
全局搜索fixupMessageRef
,发现在_read_images
调用
通过_read_images
函数的注释可知,函数的调用在map_images_nolock
,我们搜索map_images_nolock
可以发现,确实在此方法调用了_read_images
全局搜索map_images_nolock
,发现此函数调用在map_images
。
全局搜索 map_images
发现此函数在 objc_init
函数的_dyld_objc_notify_register
,继续搜索这两个函数,找不到上一级调用了。到这里,fixupMessageRef
函数调用流程锁定逆向查找就结束了。
通过上面的逆向查找可以确定,调用顺序为objc_init
->_dyld_objc_notify_register
->map_images
->map_images_nolock
->_read_images
->fixupMessageRef
接下来通过在上面方法调用处依次打上断点调试
由上面的调试,发现fixupMessageRef
并没有调用到,说明alloc
被替换为objc_alloc
并不是在fixupMessageRef
。那么苹果为什么要提供一个可能并不会调用的修复函数呢?这里我们发散一下,真正的替换可能发生在编译阶段,而这里只是做了一个容错处理呢?
我们拿到LLVM的源码,拖拽到Visual Studio Code
验证一下。
通过 MachOView
验证可执行文件,也可以发现,在编译阶段已经存在 objc_alloc
符号:
通过上面的分析可以得出,在llvm阶段已经完成了alloc
->objc_alloc
的替换,至于苹果为什么要hook这个函数,推测可能是系统对对象创建,释放等内存相关的函数进行了监控。
alloc完整流程图如下:
流程总结:
alloc
方法在编译阶段llvm会对其进行 hook
,并将alloc
方法替换为 objc_alloc
,当运行时创建一个XQPerson对象时,首先调用的是objc_alloc
,然后进入callAlloc
, 第一次判断if (fastpath(!cls->ISA()->hasCustomAWZ()))
为假,执行((id(*)(**id**, SEL))objc_msgSend)(cls, @selector(alloc));
方法,给XQPerson发送alloc消息,此时的调用流程
_objc_rootAlloc
->callAlloc
->_objc_rootAllocWithZone
->_class_createInstanceFromZone
此方法里面做三件事:计算所需空间大小
、开辟内存空间
、通过isa指针与类进行关联
。