OC对象的内存和指针
- 我们每天都在写
[[xxx alloc] init]
,但从未探究过alloc
和init
内部都干了什么?带着疑问,我们通过一段简单代码,先来看看OC
对象的初始化:
FFObj *obj = [FFObj alloc];
FFObj *o1 = [obj init];
FFObj *o2 = [obj init];
NSLog(@"%@ -- %p -- %p", obj, obj, &obj);
NSLog(@"%@ -- %p -- %p", o1, o1, &o1);
NSLog(@"%@ -- %p -- %p", o2, o2, &o2);
复制代码
- 通过上面的代码运行打印出的结果是:
<FFObj: 0x600003a28170> -- 0x600003a28170 -- 0x7ffee41dcc38
<FFObj: 0x600003a28170> -- 0x600003a28170 -- 0x7ffee41dcc30
<FFObj: 0x600003a28170> -- 0x600003a28170 -- 0x7ffee41dcc28
复制代码
- 图示:
- 结论:
- 通过三个对象的内存地址可以看出,
alloc
方法开辟了内存,而init
方法没有对内存做操作,这三个对象是一模一样的. - 通过三个指针地址可以看出,三个指针都指向了堆空间(0x6开头是堆)内的同一块区域,而三个指针自身则是在栈(0x7开头是栈)中开辟的三个连续空间.
- 通过三个对象的内存地址可以看出,
那么,alloc
是如何开辟内存空间的呢?init
又是否真的什么都没做呢?我们继续往下看.
寻找alloc
查找alloc
的位置以及底层源码调用流程,可以通过:
断点跟踪
-
先将
alloc
的调用位置添加断点 -
-
按住
control
键点击下一步,跟踪查看: -
-
查找到关键函数
objc_alloc
-
汇编分析
- 在断点位置,通过点击Xcode导航栏的
Debug->Debug Workflow->Always Show Disassembly
,查看汇编代码: - 查找到关键函数
objc_alloc
- 在断点位置,通过点击Xcode导航栏的
通过已知函数符号断点
,比如直接将alloc
添加进符号断点,然后跟踪查看.- 得到关键函数
objc_alloc
后,我们将它也加入到符号断点中:
最终得知,objc_alloc
在libobjc.A.dylib
中,而这正是objc
框架的底层源码所在,好在这部分代码苹果是开源了的,我们可以下来源码,来继续探查.
alloc源码流程分析
- 通过断点调试或者全局搜索找到
alloc
方法 -
_objc_rootAlloc
-
callAlloc
- 到了
callAlloc
之后,不再是简单的方法逐层封装调用了,而是出现了大量的逻辑,也就是说,我们要从这里开始分析源码逻辑了. - 补充说明
#if __OBJC2__
表示该代码块内的代码属于objc2.0
版本,也正是我们当前使用的版本.fastpath
的宏定义为#define fastpath(x) (__builtin_expect(bool(x), 1))
,表示当前的if
判断,有更高的几率为true
slowpath
的宏定义为#define slowpath(x) (__builtin_expect(bool(x), 0))
,表示当前的if
判断,有更高的几率为false
__builtin_expect
,用法为__builtin_expect(bool(x), y)
,表示布尔值bool(x)
有更高的几率为y
,能够让编译器在编译阶段将此处的代码跳转进行逻辑优化,得到更高性能的汇编代码.
-
_objc_rootAllocWithZone
-
_class_createInstanceFromZone
- 看来我们终于走到了
alloc
流程中最核心的部分,该方法内部进行了内存的计算和分配逻辑 instanceSize
计算所需内存空间的大小- 根据
if (zone)
判断来调用malloc_zone_calloc
或者calloc
进行内存分配 - 在
calloc
之前,分配给obj
的是一块脏内存,执行calloc
之后,obj
才真的分配到了内存,calloc
执行前后的obj
内存见下图 - 此时
po
出来的obj
是只有内存地址而没有类型的,说明此时的obj
没有绑定到类 - 通过
if (!zone && fast)
判断分别调用obj->initInstanceIsa(cls, hasCxxDtor)
或obj->initIsa(cls)
来初始化isa
,将obj
绑定到类 - 通过
if (fastpath(!hasCxxCtor))
判断,直接返回obj
或者返回object_cxxConstructFromClass(obj, cls, construct_flags)
- 看来我们终于走到了
-
instanceSize
- 当有缓存的时候,会走到
fastInstanceSize
- 没有缓存的时候,会走到
alignedInstanceSize
- 如果最终得到的
size < 16
,则会返回16
-
alignedInstanceSize
word_align
字节对齐
算法中参数x
的值取自unalignedInstanceSize()
,即data()->ro()->instanceSize
,实例变量的大小,由ivars
决定.WORD_MASK
的值在64
位中为7
,在32位中为3
-
字节对齐算法说明(
64位
)(x + WORD_MASK) & ~WORD_MASK
x = 8
WORD_MASK = 7
(8 + 7) & ~7
=15 & ~7
0000 1111 & ~0000 0111
=0000 1111 & 1111 1000
- 结果为
0000 1000
=8
- 即
(x + y) & ~y
计算的是y + 1
的整数倍数, 等同于(x + y) >>z <<z
,其中z是以2为底y的对数
,即y = 8 时 z = 3
- 由此可知,
alignedInstanceSize()
最终的计算结果是以8字节对齐
,取8的倍数
- 那么,为什么要以
8字节对齐
开辟内存呢?为什么最终分配内存如果<16
要=16
呢?- 单位长度最高为
8
,比如指针
,其他常用数据类型都可以被8
以内的大小存储下来. - 恒定以
8
为单位存储数据后,CPU
也可以恒定以8
为单位去读取数据,不需要不停地变更存取长度,这是通过以空间换时间
的方式,提高CPU
存取效率. - 以
16
为最小开辟空间是因为类会至少有一个isa
成员,而isa
是结构体指针
类型,长度为8
,开辟出更多的空间时为了容错处理.
- 单位长度最高为
-
fastInstanceSize
- 调用
align16
实现16字节对齐
-
initInstanceIsa
initInstanceIsa
会调用initIsa
-
initIsa
initIsa
会对isa
进行绑定if (!nonpointer)
判断表示是否进行指针优化
补充:alloc
流程图
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END