OC底层原理五: NSObject的alloc分析

OC底层原理 学习大纲

在编译时,发现两个问题:
问题1. NSObjectalloc方法不走常规路
问题2. 自定义类HTTestalloc方法进入callAlloc被调用2次

发现路径:

问题1:NSObjectalloc方法不走常规路

我们在[NSObject alloc]入口和alloc类方法打断点:

图一.png
image.png

发现未进入alloc类方法。于是在图一断点时,打开汇编模式

汇编模式.png

发现下一个执行函数是objc_alloc

image.png

于是我们全局搜索objc_alloc函数。在NSObject.mm文件中找到objc_alloc函数,加断点检测。

image.png

发现此时cls刚好是NSObject

NSObject alloc.png

接下来调用callAlloc函数,回归正常对象的alloc流程

image.png

新问题:NSObject是如何忽略alloc,直接调用objc_alloc函数的呢?

这里需要通过llvm源码来探究。 源码1G多,这里我展示一下就行。实际上NSObject是系统自动创建和管理的。

  • VSCode中打开llvm源码文件,搜索alloc,找到CGObjC.cpp文件夹。
image.png
  • 可以看到这里有明确标注,[self alloc] -> objc_alloc(self)
  • 函数中显示,当接收到alloc名称的selector时,调用EmitObjCAlloc函数。我们继续搜索EmitObjCAlloc
image.png
  • 在这里,我们看到发射了一个objc_alloc消息
  • 到这里,我们已经知道objc4源码中objc_alloc信号是从这发出的。
  • 具体的发送机制,后续课程会深入了解。 现在我们只要知道,NSObjectalloc是系统在llvm底层帮我们转发到objc_allocLLVM在我们编译启动时,就已经处理好了 👍

问题2:自定义类HTTestalloc方法进入callAlloc被调用2次

在探究之前,我们先做个小测试。
我们直接加入callAlloc符号断点,直接启动程序

callAlloc断点
NSArray

我们发现第一个进入cls的是NSArray,我们加设断点

image.png

发现他没有进入_objc_rootAllocWithZone,而是发送alloc的消息。

为什么?

  • 因为NSArray没有alloc方法。objc_msgSend发送alloc消息的意思是,求助系统:“老大,我要alloc一下”。这时系统会查NSArray有没有alloc方法,没有就往父类NSObject查。
  • 由于NSObject的初始化,系统在llvm编译时就已经初始化好了。所以此时会直接响应NSObjectalloc类方法。我们加入断点继续查看
image.png
image.png
image.png

我们发现,NSArrayalloc最终还是交给了系统allocWithZone去处理。😂

[HTTest alloc]

当我们加入HTTest的断点后,会经历系统的NSArray、 NSThread、NSMutableDictionary、NSSet、NSUUID的初始化,最终进入HTTestalloc方法。
与NSArray不同的是,HTTest的第二次调用是_objc_rootAllocWithZone

image.png

那么,问题还是没有回答,知道第一次是发送了alloc消息给系统。但是为什么会调用第二次?

我们回到llvm源码。

  • 上面知道了系统将alloc消息转发成objc_alloc

    image.png

  • 我们搜索tryGenerateSpecializedMessageSend函数,查看它什么时候被调用的

image.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返回falseif条件不成立,发送GenerateMessageSend常规方法,调用alloc类方法创建。

  • 此时会调用常规的alloc->_objc_rootAlloc->callAlloc流程。

拓展:

  • 当一个创建完第一个实例,再次创建第二个实例时,tryGenerateSpecializedMessageSend会返回true。我们只会响应第一次。不会调用常规的alloc方法哦。 这是缓存的作用。后面熟悉cacheobjc_msgSend机制后,相信你就懂了。
    如:
HTTest * test1  = [HTTest alloc];
HTTest * test2  = [HTTest alloc];

总结

  • NSObject的创建由系统处理
  • 自定义类alloc创建时,会执行两次,第一次判断条件时执行,第二次常规方法执行。

下一节,OC底层原理六: 内存对齐

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容