- 先前在alloc init new方法的探索这篇文章中详细阐述了NSObject的alloc方法底层的调用流程,但是不够完善,本篇来探讨自定义对象 alloc方法的底层执行逻辑,首先来回顾一下NSObject的alloc方法;
自定义类YYPerson对象的alloc探索
- 将工程中NSObject改成YYPerson,如下所示:

image.png
先将源码中断点置为
Disable,重新运行工程,当断点停在main函数的YYPerson *p1 = [YYPerson alloc]时,再将源码中断点打开,这么做是为了保证执行到objc_alloc函数时传进来的参数是YYPerson;接下来的执行流程:
callAlloc->objc_msgSend()->alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone-> 完成YYPerson对象的alloc流程;可以看出YYPerson的alloc与NSObject的alloc,底层执行流程不太一样,在YYPerson的alloc中 函数
callAlloc执行了两次,而在NSObject的alloc中函数callAlloc执行了一次,这是最大的区别;在YYPerson的alloc中,两次执行
callAlloc函数时,其内部逻辑为如下图所示:

YYPerson_alloc.png
- 看到这里提出三个问题?
- 问题一:无论是NSObject系统对象还是自定义YYPerson对象,alloc时底层为什么会首先调用
objc_alloc函数而不是调用alloc的函数实现? - 问题二:NSObject系统对象和自定义YYPerson对象的alloc底层调用逻辑为什么会存在差异?
- 问题三:自定义YYPerson对象在第一次alloc时,callAlloc函数会调用两次,为什么在第二次alloc时,callAlloc函数只调用一次?
- 问题一:无论是NSObject系统对象还是自定义YYPerson对象,alloc时底层为什么会首先调用
针对问题一:alloc函数,底层并没有去调用自己的实现而是去调用了objc_alloc函数,猜想很有可能是系统利用Runtime做了method swizzle(方法交换),这里需要借助LLVM源码进行进一步的分析,LLVM的源码下载很耗时,我借助了别人的截图;
- 打开llvm源码文件,搜索
alloc,找到CGObjC.cpp文件

Snip20210204_56.png
- 可以看到这里有明确标注,
[self alloc] -> objc_alloc(self) - 函数中显示,当接收到alloc名称的selector时,调用
EmitObjCAlloc函数,我们继续搜索EmitObjCAlloc;

Snip20210204_57.png
- 在这里,我们看到发送了一个
objc_alloc消息; - 到这里,我们已经知道objc4源码中objc_alloc信号是从这发出的;
- 具体的发送机制,后续会做详细的阐述,现在我们只要知道
NSObject的alloc和自定义YYPerson的alloc是系统在LLVM底层做了处理帮我们转发到objc_alloc函数,LLVM在我们编译启动时,就已经处理好了;
针对问题二:现在我们知道对象alloc底层调用:
- 首先会被LLVM处理转发到
objc_alloc函数去执行,所以第一次调用callAlloc函数即objc_alloc -> callAlloc, - 然后判断
!cls->ISA()->hasCustomAWZ():即当前类的cache_t中是否存在alloc/allocWithZone方法,没有就会进入objc_msgSend(cls, @selector(alloc),第二次调用callAlloc函数即alloc -> _objc_rootAlloc -> callAlloc,并将alloc方法缓存到cache_t中; - 系统类NSObject,cache_t缓存中默认是存在alloc/allocWithZone方法;
- 自定义类YYPerson,cache_t缓存中默认没有alloc/allocWithZone方法;
针对问题三
- 当YYPerson类在第一次alloc时,会两次调用
callAlloc函数; - 当YYPerson类在第二次alloc时,首先消息转发成
objc_alloc消息,所以第一次调用callAlloc函数即objc_alloc -> callAlloc,由于第一次alloc调用之后 cache_t会进行缓存,再次判断!cls->ISA()->hasCustomAWZ():即当前类的cache_t中是否存在alloc/allocWithZone方法,存在,就不会再执行objc_msgSend(cls, @selector(alloc),所以callAlloc函数只会调用一次;