iOS开发的小伙伴们对 [XXX alloc] init]
都不陌生,可以说 alloc
和 init
贯穿我们整个的开发过程中。那么在OC对象的底层,到底做了哪些操作呢?今天我们就来探索一下 alloc
底层的工作流程。
一、抛砖引玉
我们先来看一下下面这张图中的测试代码和打印结果:
从上面的打印结果来看,p、p1、p2
对象的内存地址是一样的,但是p、p1、p2
对象的指针地址(&p、&p1、&p2
)是不同的。而pNew
对象的内存地址和指针地址和p、p1、p2
都不一样,很显然,pNew
属于拥有另一块内存空间的另一个对象了。
由此我们暂时得出结论:
p、p1、p2
对象的指针地址是不同的, 但是他们都指向同一内存空间;alloc
可以开辟内存空间,而init
不会;p、p1、p2
对象的指针地址&p > &p1 > &p2 > &pNew
,说明栈区是由高到低连续开辟的
;p 、pNew
对象的内存地址p < pNew
,说明堆区是由低到高开辟内存的
。
结合堆栈的知识 ,我画了下面👇这张图,帮助大家理解。
二、准备工作
通过上面我们可以发现,对象内存地址是通过 alloc
创建,我们看一下 alloc
是怎么实现的。
点击 alloc
方法进入 NSObject.h
:
进入NSObject.h
,我们再点击跳转,发现跳转不进去了,也就看不到alloc
的实现了。难道我们就只能停在这里?就只能在外面蹭一蹭了吗?
NO,下面来介绍一下探索底层的三种方法,方便我们在探索底层源码的时候能够顺利的跟对方法(函数)的一个执行流程。
第一种:添加符合断点方式
- 在工程中选择断点 --> 点击左下角"+" --> Symbolic Breakpoint
- 比如我这里想知道
alloc
源码位置, 那么就输入alloc
- 然后运行, 我们发现
alloc
的符号断点非常多,到底哪个才是我们想要的呢?
- 接着我们还需要在想要执行的代码处增加一个普通断点,比如我们这里在
JQPerson
的alloc
处打上一个断点,然后将alloc
符号断点先禁用
6.png
- 运行程序,首先来到我们的普通断点
[JQPerson alloc]
处,然后我们将符号断点alloc启用,点击断点操作按钮进入下一步
到这里,我们可以看到alloc
方法在libobjc.A.dylib
库中(ps:libobjc.A.dylib是objc的系统库,感兴趣的小伙伴可以去苹果开源官网Open Source下载查看,注意:Open Source上下载下来的源码是不能直接编译和调试的,想要下载的objc源码可编译调试的小伙伴可以移步到我之前的文章iOS底层原理(一):苹果开源 objc4-818 源码项目的编译和调试)
第二种: 断点 + step into方式
- 我们先在要执行的代码打上断点,运行项目,来到断点位置
- 然后按住control键,点击setp into一步一步查找,会看到如下结果
- 最后再添加
objc_alloc
符号断点,点击Continue program execution继续执行
这里我们可以看到,断点进入了libobjc.A.dylib
中的objc_alloc
函数,由此可知alloc
方法的源码在libobjc.A.dylib
库中。
第三种: 汇编跟进方式
- 首先,我们还是先在要执行的代码打上断点
- 然后在Xcode菜单栏找到 Debug ==> Debug Workflow ==> Always Show Disassembly并选中(这里是启用汇编进行调试)
- 运行项目,来到如下图的断点处
我们可以看到当前断点下面两行处,有个callq xxxx; symbol stub for objc_alloc
,接着我们再添加一个objc_alloc
符号断点, 点击Continue program execution继续执行(ps:这里解释一下:callq
是汇编中的一个指令,代表这个这里即将要调用一个方法,symbol stub for objc_alloc
翻译过来是objc_alloc
的符号存根,也就是说objc_alloc
是要调用的方法名)
好了,到此底层探索的三种方式就介绍完了,接下来我们步入正题吧!
三、alloc源码探索
好的,有了上面的探索方法,我们现在就拿 objc
源码项目来探索 alloc
的底层实现吧。
首先,打开之前编译好的 objc4-818.2 项目,需要的小伙伴可以参考我之前文章iOS底层原理(一):苹果开源 objc4-818 源码项目的编译和调试,到 Open Source 上下载源码自行编译,不想麻烦的也可以直接去 GitHub 上下载:JQObjc4-818.2BuildDebug。
然后,找到 JQObjcBuildDemo 目录下创建一个JQPerson
类。然后在main.m中添加如下代码:
注意: 这里 16、17行
分别有个断点,后面会用到!!!
我们从上面的底层探索方式中可以看到:[JQPerson alloc]
在底层libobjc.A.dylib
库中执行的objc_alloc
方法,接下来我就来验证一下。
第1步:alloc 和 objc_alloc
- 点击
alloc
跳转到 objc 的源码中,搜索一下objc_alloc
,然后分别在alloc
和objc_alloc
处打上断点
- 然后,先将源码中
alloc
和objc_alloc
处的断点禁用,运行项目来到main.m中的断点处
- 接着,启用源码中
alloc
和objc_alloc
处的断点,点击下一步,这时会发现:断点来到了objc_alloc
处
这就验证了我们前面所讲的,alloc
方法在底层libobjc.A.dylib
库中执行的objc_alloc
方法 !
- 再次点击下一步,惊奇的发现:断点来到了
alloc
方法处
那么为什么[JQPerson alloc]
在底层会先走objc_alloc
方法,再走alloc
方法呢?按照我们在 objc 源码中看到的方法调用流程,应该是[JQPerson alloc] => alloc
呀?
为了验证这个问题,我们需要请出YYDS(永远滴神):llvm源码(是苹果开源的系统级别的源码),看一看苹果是不是在这里面做了什么骚操作。llvm-project下载地址