在上一篇文章中以[LBHPerson alloc]
为例对+alloc
方法进行了源码分析,本文作为补充去探索作为根类的NSObject
的[NSObject alloc]
流程与[LBHPerson alloc]
流程是否有区别。
源码分析
沿用上一篇文章的objc4-781源码,新增一个NSObject的实例并打上断点

根据上一篇文章分析的alloc流程

等程序执行到main
函数的NSObject *obj = [NSObject alloc]
的断点时, 将上面流程中所有的方法打上断点继续运行,会发现执行完callAlloc
方法后直接执行_objc_rootAllocWithZone
方法,即直接从第二步到第六步,并没有走+ alloc
流程,可以得出[NSObject alloc]
的流程图

问题
1、为什么明明调用的是+ alloc
方法首先执行的却是 objc_alloc
方法?
2、为什么[LBHPerson alloc]
的流程中callAlloc
调用两次?
3、为什么[NSObject alloc]
流程没有执行+ alloc
方法?
分析
问题1
、为什么明明调用的是+ alloc
方法首先执行的却是 objc_alloc
方法?
准备工作
action
step1:
在vscode工具中打开llvm源码,搜索omf_alloc:
找到tryGenerateSpecializedMessageSend
,表示尝试生成特殊消息发送

当接收到alloc
名称的selector时,调用EmitObjCAlloc
函数。
step2:
跳转至EmitObjCAlloc
的定义可以看到alloc
的处理是调用了 objc_alloc

由此可以得出:llvm在编译启动时,alloc
方法会被转换为objc_alloc
方法
问题2
、为什么[NSObject alloc]
流程没有执行+ alloc
方法?
action
step1:
在objc4-781源码中,打上以后几个断点,然后运行

会发现断点并没有进入到main函数里,而是进入objc_alloc
,cls
为NSArray
,这意味系统在初始化做了很多工作,我们来看下这个[NSArray alloc]
的调用情况

step2:
在callAlloc
方法根据断点一步步往下走,会进入objc_msgSend
消息发送,

step3:
+alloc方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
step4:
_objc_rootAlloc方法
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
step5:
callAlloc方法,同step2
调用的是同一个方法,我们跟着断点一步步走,最后通过objc_msgSend
消息发送调用系统的allocWithZone
方法。

看一下它的堆栈调用情况

在step2
中objc_msgSend
发送alloc
消息时,当前的cls
为NSArray
,类中没有自定义的+ alloc
方法,所以会去它的父类也就是NSObject
中查找,堆栈调用显示的是调用的NSObject
的+alloc
方法,还没到main
函数中手动调用[NSObject alloc]
时系统就已经处理好了。
step6:
先暂时关掉非main
函数中的断点,等程序执行到main
函数断点,再开启这些断点,着重看一下callAlloc
函数

由此可以得出:
系统初始化时NSObject
已经完成+alloc
操作
问题3
、为什么[LBHPerson alloc]的流程中callAlloc调用两次?
action
step1:
回到llvm源码,经过前面的分析已经知道llvm会将+alloc
转发成objc_alloc
,找到实现它的方法

step2:
搜索tryGenerateSpecializedMessageSend
,看它的调用情况

- 在
GeneratePossiblySpecializedMessageSend
中tryGenerateSpecializedMessageSend
被调用,说明runtime消息处理时必先调用这个函数 - 如果满足
if
条件,则调用特殊消息发送,即将alloc
转发成objc_alloc
- 如果不满足,则调用普通消息发送
由此可以得出:
第一次调用+alloc
方法会调用特殊消息处理,即将alloc
转发成objc_alloc
;第二次会调用普通消息处理
总结
- NSObject的创建由系统处理
- 自定义类alloc创建时,会执行两次,第一次判断条件时执行,第二次常规方法执行。
网友评论