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底层做了哪些操作?
下一章节来解答疑惑