OC底层原理五: NSObject的alloc分析
在编译时,发现两个问题:
问题1. NSObject
的alloc
方法不走常规路
问题2. 自定义类HTTest
的alloc
方法进入callAlloc
被调用2次
发现路径:
问题1:NSObject
的alloc
方法不走常规路
我们在[NSObject alloc]
入口和alloc
类方法打断点:
![](https://img.haomeiwen.com/i12857030/7265c052acbd752c.png)
![](https://img.haomeiwen.com/i12857030/d8a4e105445c85ad.png)
发现未进入alloc
类方法。于是在图一断点
时,打开汇编模式
![](https://img.haomeiwen.com/i12857030/09dcec1b4470fc09.png)
发现下一个执行函数是objc_alloc
![](https://img.haomeiwen.com/i12857030/08297ab78b2fb463.png)
于是我们全局搜索objc_alloc
函数。在NSObject.mm
文件中找到objc_alloc
函数,加断点检测。
![](https://img.haomeiwen.com/i12857030/9caffdae3d9cd761.png)
发现此时cls
刚好是NSObject
。
![](https://img.haomeiwen.com/i12857030/69c200963b886030.png)
接下来调用callAlloc
函数,回归正常对象的alloc
流程
![](https://img.haomeiwen.com/i12857030/84db9e56ae4c69f4.png)
新问题:NSObject
是如何忽略alloc
,直接调用objc_alloc
函数的呢?
这里需要通过llvm
源码来探究。 源码1G多,这里我展示一下
就行。实际上NSObject
是系统自动创建和管理的。
- 在
VSCode
中打开llvm
源码文件,搜索alloc
,找到CGObjC.cpp
文件夹。
![](https://img.haomeiwen.com/i12857030/30b8c2d7249e42b7.png)
- 可以看到这里有明确标注,
[self alloc] -> objc_alloc(self)
- 函数中显示,当接收到
alloc
名称的selector
时,调用EmitObjCAlloc
函数。我们继续搜索EmitObjCAlloc
:
![](https://img.haomeiwen.com/i12857030/210cb0773121aa56.png)
- 在这里,我们看到
发射
了一个objc_alloc
消息 - 到这里,我们已经知道
objc4
源码中objc_alloc
信号是从这发出的。 - 具体的发送机制,后续课程会深入了解。 现在我们只要知道,
NSObject
的alloc
是系统在llvm
底层帮我们转发到objc_alloc
。LLVM
在我们编译启动
时,就已经处理好了 👍
问题2:自定义类HTTest
的alloc
方法进入callAlloc
被调用2次
在探究之前,我们先做个小测试。
我们直接加入callAlloc
符号断点,直接启动程序
![](https://img.haomeiwen.com/i12857030/7a28f8eb4fb8dbc5.png)
![](https://img.haomeiwen.com/i12857030/b4700b383765e460.png)
我们发现第一个进入cls
的是NSArray
,我们加设断点
。
![](https://img.haomeiwen.com/i12857030/f758df3a34895123.png)
发现他没有进入_objc_rootAllocWithZone
,而是发送alloc
的消息。
为什么?
- 因为NSArray没有
alloc
方法。objc_msgSend
发送alloc
消息的意思是,求助系统:“老大,我要alloc
一下”。这时系统会查NSArray
有没有alloc
方法,没有就往父类NSObject
查。 - 由于
NSObject
的初始化,系统在llvm编译时就已经初始化好了。所以此时会直接响应NSObject
的alloc
类方法。我们加入断点
继续查看
![](https://img.haomeiwen.com/i12857030/bad338f96bf371bd.png)
![](https://img.haomeiwen.com/i12857030/85546af5f02bb5b0.png)
![](https://img.haomeiwen.com/i12857030/7a35f22a4d13eb94.png)
我们发现,NSArray
的alloc
最终还是交给了系统allocWithZone
去处理。😂
[HTTest alloc]
当我们加入HTTest
的断点后,会经历系统的NSArray、 NSThread、NSMutableDictionary、NSSet、NSUUID
的初始化,最终进入HTTest
的alloc
方法。
与NSArray不同的是,HTTest
的第二次调用是_objc_rootAllocWithZone
![](https://img.haomeiwen.com/i12857030/5702e28ac1641026.png)
那么,问题还是没有回答,知道第一次是发送了alloc
消息给系统。但是为什么会调用第二次?
我们回到llvm
源码。
-
上面知道了系统将
alloc
消息转发成objc_alloc
image.png
-
我们搜索
tryGenerateSpecializedMessageSend
函数,查看它什么时候被调用的
![](https://img.haomeiwen.com/i12857030/c9effc0d74b1ae8b.png)
-
我们任何runtime消息被处理时,都会先来到这个函数
GeneratePossiblySpecializedMessageSend
。 -
看到上面
被调用的函数
在这里作为了if判断条件
。条件每次都会try先尝试一遍
,所以这里会调用objc_alloc
。 -
结果为
false
(没找到),就会执行普通消息发送GenerateMessageSend
,就会进入当前的alloc
流程。
实例检验
image.png
断点
进入的顺序
如下:image.png
可以发现,任何类
调用alloc
时,并不会
先进入Alloc
类方法直接执行
系统使用
objc_msgSend
消息机制发送了alloc
消息消息会先到达
llvm层
的GeneratePossiblySpecializedMessageSend
函数,条件判断中触发tryGenerateSpecializedMessageSend
函数
tryGenerateSpecializedMessageSend
函数内部将alloc
消息转发成了objc_alloc
消息。所以我们第一次响应是objc_alloc
->callAlloc
第一次时,类没有实现,所以
tryGenerateSpecializedMessageSend
返回false
,if
条件不成立,发送GenerateMessageSend
常规方法,调用alloc
类方法创建。此时会调用常规的
alloc
->_objc_rootAlloc
->callAlloc
流程。
拓展:
- 当一个
类
创建完第一个实例
,再次创建第二个实例
时,tryGenerateSpecializedMessageSend
会返回true
。我们只会响应第一次
。不会调用常规的alloc
方法哦。 这是缓存
的作用。后面熟悉cache
和objc_msgSend
机制后,相信你就懂了。
如:HTTest * test1 = [HTTest alloc]; HTTest * test2 = [HTTest alloc];
总结
-
NSObject
的创建由系统处理 - 自定义类
alloc
创建时,会执行两次,第一次判断条件
时执行,第二次常规方法
执行。
下一节,OC底层原理六: 内存对齐