探究的背景
猜猜下面的代码运行的结果
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
和你预想的结果是一样的吗?我们可以得出如下的结论
p1、p2、p3指向的是同一块内存地址,也就是指向的是同一个对象,由此可以看出alloc具备开辟内存的能力, 而init不具备开辟内存的能力p1、p2、p3指针的地址是不一样的,但可以看出开辟的是连续的栈空间,由高到低排列相差8个字节
究竟为什么会这样呢,直接查看alloc函数,发现只有一个函数声明,不要慌,开始我们的 alloc底层探索 之旅吧
三种探索定位底层的方法
1. 符号断点
在需要的探索的位置打上断点,当断点被lldb断住时,按住control键,再点击 step into
进入汇编代码调试页面,在汇编里找到方法名称,然后下符号断点。具体操作如下:
2. 汇编+符号断点
通过汇编调式方式找到入口方法,然后下符号断点。
具体操作如下:
后面的操作就和方式1一样了
3. 已知位置+符号断点
这个是在已知方法的前提下,直接下方法的符号断点, 进而一步一步追踪到底层调用。
具体操作如下:
源码探索(值得拥有)
通过上面的任意一种方式,我们都可以定位到底层alloc的实现流程,但是作为一名开发者,我们更想知道具体的实现流程,接下来我们通过objc源码窥探alloc的实现流程
- 首先登录苹果的OpenSource或者Source Browser
(推荐这个)下载objc源码,我下的是objc4-8182版本,具体步骤流如下:
注意可以使用
command+f搜索objc就能快速定位到当前源码
- 阅读源码结合符合断点验证
alloc底层流程
打开源码,由于我们探索的是alloc的实现流程,YYPerson里面并没有alloc的具体实现,那么该方法的实现一定是在NSObject父类中,我们可以搜索 alloc { ,具体流程如下:
跟踪源码我们发现alloc的调用流程 alloc->_objc_rootAlloc-> callAlloc,但是在callAlloc函数调用中,出现了判断调用,究竟会走_objc_rootAllocWithZone还是会走objc_msgSend呢,我们可以通过符号断点来验证,当某个对象的alloc函数被断住时,依次下上alloc 、_objc_rootAlloc、 callAlloc 三个符号断点,然后next,具体操作如下:
通过符号断点我们发现 走的是_objc_rootAllocWithZone 这个方法,但是符号断点callAlloc 没被断住,这里涉及到编译器优化,后面有时间在研究
- 源码+断点(爽歪歪)
源码结合符号断点虽然不错,但是遇到判断过多的方法,探究起来势必会麻烦,要是能直接断点调试,探究起来会更方便,直接下断点(具体源码配置,后面会有文章来介绍)。下面我们就开始通过源码下断点的方式,开启探索
alloc的底层流程之旅吧,详细步骤如下:
重要事情讲三遍,当要探究的
alloc对象被断住时,才能打开这些断点哟
通过断点调试,我发现首次对象调用alloc会从objc_alloc-> callAlloc->alloc-> _objc_rootAlloc -> callAlloc-> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize 、calloc、initInstanceIsa
如果再次让这个对象调用alloc 这个对象 直接回跳过上面的步骤 1、2、3、4 从 5 开始alloc对象,大致的逻辑流程图如下:
为什么会有这样的现象呢? 由于LLVM底层Hook住了NSObject的alloc函数,做了优化操作,具体更详细的可以去下载LLVM源码,做更深入的了解。通过源码调试,还能能追踪定位更多现象的。
至此,alloc的底层流程就算窥探完了,感谢您的阅读,如有不对之处,还请多多指出~