OC对象底层内存开辟和实现(上)

1、alloc、init、new会有什么区别,可以说 allocinit 贯穿我们整个的开发过程中。那么在OC对象的底层,到底做了哪些操作呢?今天我们来梳理一下底层的工作流程。

在xcode中创建工程来打印一下对象地址指针本身地址,代码如下:

Car *c1 = [Car alloc];
Car *c2 = [c1 init];
Car *c3 = [c1 init];
Car *c4 = [Car new];

NSLog(@"\n%@--->%p-%p",c1,c1,&c1);
NSLog(@"\n%@--->%p-%p",c2,c2,&c2);
NSLog(@"\n%@--->%p-%p",c3,c3,&c3);
NSLog(@"\n%@--->%p-%p",c4,c4,&c4);
复制代码

输出结果如下:

<Car: 0x6000012d4670>--->0x6000012d4670-0x7ffeec5fc008
<Car: 0x6000012d4670>--->0x6000012d4670-0x7ffeec5fc000
<Car: 0x6000012d4670>--->0x6000012d4670-0x7ffeec5fbff8
<Car: 0x6000012d4680>--->0x6000012d4680-0x7ffeec5fbff0
复制代码

从打印结果来看 c1、c2、c3 对象的内存地址是一样的;c1、c2、c3 对象的指针地址( &c1、&c2、&c3)是不同的;而c4对象的内存地址和指针地址和c1、c2、c3都不一样,说明new后属于拥有另一块内存空间的另一个对象了。

由此得出结论:

  • c1、c2、c3对象的指针地址是不同的, 但是他们都指向同一内存空间;
  • alloc 可以开辟内存空间,而 init不会再开辟内存空间;
  • c1、c2、c3对象的指针地址 &c1 > &c2 > &c3 > &new,说明栈区是由高到低连续开辟的
  • c1 、c4对象的内存地址c1 < c4,说明堆区是由低到高开辟内存的

图示如下:

WechatIMG191.jpeg

2、通过断点和汇编的方式查看alloc进行了哪些操作

第一、断点后 control+step into方式,查看执行过程

  • 在要执行的代码的地方打上断点,运行项目执行到断点位置,当断点断住的时候按住control键,然后step into 进入下一步,然后在汇编里面就可以看到执行的函数,具体流程如下图:

WechatIMG192.png

WechatIMG193.png

  • 此时我们可以看到函数执行了“objc_alloc”函数(并非我们想看到的的alloc函数),我们可以给这个方法添加符号断点,点击Continue program execution继续执行

WechatIMG194.png 小结: 我们可以看到,断点进入了libobjc.A.dylib中的objc_alloc函数,由此我们可以推断alloc方法的源码在libobjc.A.dylib库中。    

第二、直接通过汇编跟进调试方式,查看执行过程

  • 首先还是在要执行的代码的地方打上断点,然后在Xcode -> Debug -> Debug Workflow -> Always Show Disassembly 进入汇编跟进调试模式,可以根据汇编代码分析要执行的流程

WechatIMG197.png

WechatIMG195.png

  • 我们可以看到当前断点下面有行汇编代码callq 0x1056213c6; symbol stub for objc_alloc(callq是汇编指令,代表将要调用这个方法),此时有两种操作方式:

1、添加一个objc_alloc符号断点, 点击Continue program execution继续执行,直接进入到了libobjc.A.dylib中的objc_alloc函数;

WechatIMG198.png

2、按住control键,然后step into 进入下一步(只不过最后进入objc_alloc函数后 还是同“第一种方式”,查看源码所在库)

WechatIMG196.png

WechatIMG193.png 此时还是需要打符号断点进行查看执行(同第一种方式)。

3、objc4源码分析

WechatIMG199.png

WechatIMG200.png 目前最新objc4版本为objc4-818.4,我们以最新版本为例分析。

  • 首先,打开之前编译好的 objc4-818源码项目,在KCObjcBuild 目录下创建LGPerson类,并在main函数中实现alloc创建实例(如图打上两个断点 后面会用到)。

image.png

  • 查看探索源码工程代码执行流程(非调试情况下),点击alloc执行Jump to Definition逐级跳转 alloc代码调用流程如下(画流程图展示):

image.png

问题来了:我们调试的时候明明调用了函数objc_alloc,此时整个代码流程并没有这个函数,为什么呢?

  • 我们在main函数中打上断点运行项目,代码执行来到断点处

image.png

  • 此时我们在分别在allocobjc_alloc两个函数打上断点调试一下,执行main函数中代码,执行如下:

image.png

image.png

这个时候我们发现代码执行了objc_alloc,并没有执行alloc函数,验证了前面我们用汇编和断点调试的结论,确实执行了libobjc.A.dylib中的objc_alloc函数。

  • 我们继续执行下一步,在callAlloc函数中执行objc_msgSend消息发送来到了alloc函数中:

image.png image.png

经断点调试,执行objc_alloccallAlloc确实走到了发送alloc消息这一行,也就是 [LGPerson alloc] -> objc_alloc -> callAlloc

  • 我们继续执行发现[LGPerson alloc] -> objc_alloc -> callAlloc -> alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone>_class_createInstanceFromZone

image.png

  • 当前我们执行完 main.m中的27行实例p的alloc,断点会来到18行执行p1实例的alloc

  • 继续执行断点,会发现执行流程变为:[LGPerson alloc] ->objc_alloc -> callAlloc -> _objc_rootAllocWithZone,不再走alloc函数流程。

image.png

image.png

image.png

我们继续执行,发现NSOject类第一次执行时,执行流程也为[NSOject alloc] ->objc_alloc -> callAlloc -> _objc_rootAllocWithZone,不再执行alloc函数。

4、此时我们会有几个问题?

1、LGPerson第一次alloc为什么会先执行objc_alloc,再执行alloc?

2、LGPerson第二次alloc为什么执行objc_alloc后,不再执行alloc?

3、NSOject第一次alloc为什么执行objc_alloc后,不再执行alloc?

4、init和new底层做了哪些操作?

下一章节来解答疑惑

猜你喜欢

转载自juejin.im/post/7104694294157459469