1、alloc、init、new会有什么区别,可以说 alloc 和 init 贯穿我们整个的开发过程中。那么在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,说明堆区是由低到高开辟内存的。
图示如下:
2、通过断点和汇编的方式查看alloc进行了哪些操作
第一、断点后 control+step into方式,查看执行过程
- 在要执行的代码的地方打上断点,运行项目执行到断点位置,当断点断住的时候按住
control键,然后step into进入下一步,然后在汇编里面就可以看到执行的函数,具体流程如下图:
- 此时我们可以看到函数执行了
“objc_alloc”函数(并非我们想看到的的alloc函数),我们可以给这个方法添加符号断点,点击Continue program execution继续执行
小结:
我们可以看到,断点进入了
libobjc.A.dylib中的objc_alloc函数,由此我们可以推断alloc方法的源码在libobjc.A.dylib库中。
第二、直接通过汇编跟进调试方式,查看执行过程
- 首先还是在要执行的代码的地方打上断点,然后在
Xcode->Debug->Debug Workflow->Always Show Disassembly进入汇编跟进调试模式,可以根据汇编代码分析要执行的流程
- 我们可以看到当前断点下面有行汇编代码
callq 0x1056213c6; symbol stub for objc_alloc(callq是汇编指令,代表将要调用这个方法),此时有两种操作方式:
1、添加一个
objc_alloc符号断点, 点击Continue program execution继续执行,直接进入到了libobjc.A.dylib中的objc_alloc函数;
2、按住
control键,然后step into进入下一步(只不过最后进入objc_alloc函数后 还是同“第一种方式”,查看源码所在库)
此时还是需要打符号断点进行查看执行(同第一种方式)。
3、objc4源码分析
- 源码代码库下载地址:opensource.apple.com/tarballs/
目前最新objc4版本为
objc4-818.4,我们以最新版本为例分析。
- 注意:下载好的源码是不可以调试的,编译也会报错。可以参考大神Cooci编写的"objc4-756.2 最新源码编译调试"
- 首先,打开之前编译好的
objc4-818源码项目,在KCObjcBuild目录下创建LGPerson类,并在main函数中实现alloc创建实例(如图打上两个断点 后面会用到)。
- 查看探索源码工程代码执行流程(非调试情况下),点击
alloc执行Jump to Definition逐级跳转 alloc代码调用流程如下(画流程图展示):
问题来了:我们调试的时候明明调用了函数objc_alloc,此时整个代码流程并没有这个函数,为什么呢?
- 我们在main函数中打上断点运行项目,代码执行来到断点处
- 此时我们在分别在
alloc和objc_alloc两个函数打上断点调试一下,执行main函数中代码,执行如下:
这个时候我们发现代码执行了
objc_alloc,并没有执行alloc函数,验证了前面我们用汇编和断点调试的结论,确实执行了libobjc.A.dylib中的objc_alloc函数。
- 我们继续执行下一步,在
callAlloc函数中执行objc_msgSend消息发送来到了alloc函数中:
经断点调试,执行
objc_alloc后callAlloc确实走到了发送alloc消息这一行,也就是[LGPerson alloc]->objc_alloc->callAlloc
- 我们继续执行发现
[LGPerson alloc]->objc_alloc->callAlloc->alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone>_class_createInstanceFromZone
-
当前我们执行完 main.m中的
27行实例p的alloc,断点会来到18行执行p1实例的alloc。 -
继续执行断点,会发现执行流程变为:
[LGPerson alloc]->objc_alloc->callAlloc->_objc_rootAllocWithZone,不再走alloc函数流程。
我们继续执行,发现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底层做了哪些操作?
下一章节来解答疑惑