[iOS进阶] OC对象alloc底层流程窥探

213 阅读4分钟

探究的背景

猜猜下面的代码运行的结果

YYPerson *p1 = [YYPerson alloc];
YYPerson *p2 = [p1 init];
YYPerson *p3 = [p1 init];

NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
<YYPerson: 0x60000253c4c0>-0x60000253c4c0-0x7ffee068b078
<YYPerson: 0x60000253c4c0>-0x60000253c4c0-0x7ffee068b070
<YYPerson: 0x60000253c4c0>-0x60000253c4c0-0x7ffee068b068

和你预想的结果是一样的吗?我们可以得出如下的结论

  • p1p2p3 指向的是同一块内存地址,也就是指向的是同一个对象,由此可以看出 alloc 具备开辟内存的能力, 而 init 不具备开辟内存的能力
  • p1p2p3 指针的地址是不一样的,但可以看出开辟的是连续的空间,由高到低排列 相差8个字节

究竟为什么会这样呢,直接查看alloc函数,发现只有一个函数声明,不要慌,开始我们的 alloc底层探索 之旅吧

三种探索定位底层的方法

1. 符号断点

在需要的探索的位置打上断点,当断点被lldb断住时,按住control键,再点击 step into
进入汇编代码调试页面,在汇编里找到方法名称,然后下符号断点。具体操作如下:

符号断点01.jpeg

符号断点02.jpeg

符号断点03.jpeg

符号断点04.jpeg

符号005.jpeg

2. 汇编+符号断点

通过汇编调式方式找到入口方法,然后下符号断点。
具体操作如下:

汇编01.jpg

汇编02.jpg 后面的操作就和方式1一样了

3. 已知位置+符号断点

这个是在已知方法的前提下,直接下方法的符号断点, 进而一步一步追踪到底层调用。
具体操作如下:

已知位置01.jpeg

已知位置02.jpg

已知位置04.jpeg

符号断点04.jpeg

符号005.jpeg

源码探索(值得拥有)

通过上面的任意一种方式,我们都可以定位到底层alloc的实现流程,但是作为一名开发者,我们更想知道具体的实现流程,接下来我们通过objc源码窥探alloc的实现流程

  1. 首先登录苹果的OpenSource或者Source Browser(推荐这个) 下载objc源码,我下的是objc4-8182版本,具体步骤流如下:

源码01.jpg

源码截图02.jpg

注意可以使用command+f搜索objc就能快速定位到当前源码

  1. 阅读源码结合符合断点验证alloc底层流程

打开源码,由于我们探索的是alloc的实现流程,YYPerson里面并没有alloc的具体实现,那么该方法的实现一定是在NSObject父类中,我们可以搜索 alloc { ,具体流程如下:

源码调试01.jpg

源码调试02.jpg

源码调试03.jpg

跟踪源码我们发现alloc的调用流程 alloc->_objc_rootAlloc-> callAlloc,但是在callAlloc函数调用中,出现了判断调用,究竟会走_objc_rootAllocWithZone还是会走objc_msgSend呢,我们可以通过符号断点来验证,当某个对象的alloc函数被断住时,依次下上alloc_objc_rootAlloccallAlloc 三个符号断点,然后next,具体操作如下:

源码1-001.jpg

源码1-002.jpg

通过符号断点我们发现 走的是_objc_rootAllocWithZone 这个方法,但是符号断点callAlloc 没被断住,这里涉及到编译器优化,后面有时间在研究

  1. 源码+断点(爽歪歪) 源码结合符号断点虽然不错,但是遇到判断过多的方法,探究起来势必会麻烦,要是能直接断点调试,探究起来会更方便,直接下断点(具体源码配置,后面会有文章来介绍)。下面我们就开始通过源码下断点的方式,开启探索alloc的底层流程之旅吧,详细步骤如下:

源码断点01.jpg

源码断点02.jpg

源码断点03.jpg

源码断点04.jpg

源码断点05.jpg

源码断点6.jpg

源码断点7.jpg

源码符号断点7-1.jpg

源码断点7-2.jpg

源码断点7-3.jpg

重要事情讲三遍,当要探究的alloc对象被断住时,才能打开这些断点哟

通过断点调试,我发现首次对象调用alloc会从objc_alloc-> callAlloc->alloc-> _objc_rootAlloc -> callAlloc-> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSizecallocinitInstanceIsa 如果再次让这个对象调用alloc 这个对象 直接回跳过上面的步骤 12345 开始alloc对象,大致的逻辑流程图如下:

alloc底层初始化流程.jpg

为什么会有这样的现象呢? 由于LLVM底层Hook住了NSObjectalloc函数,做了优化操作,具体更详细的可以去下载LLVM源码,做更深入的了解。通过源码调试,还能能追踪定位更多现象的。 至此,alloc的底层流程就算窥探完了,感谢您的阅读,如有不对之处,还请多多指出~