阅读 736

iOS底层 - alloc和init探索

 欢迎阅读iOS底层系列(建议按顺序)
iOS底层 - alloc和init探索
iOS底层 - 包罗万象的isa
iOS底层 - 类的本质分析
iOS底层 - cache_t流程分析
iOS底层 - 方法的本质和查找流程分析


       众所周知,alloc和init是我们最熟悉的最简单的api,那你是否完全了解它呢,还是它是你最熟悉的陌生人。我们就从源码入手, 看看alloc和init究竟分别做了什么?

想要明白一段代码究竟做了什么,最直接就是看底层源码解决,但是直接点击alloc会发现来到这里,看不到底层代码,有四种方法解决

1.下断点:control + in 到objc_alloc

2.直接下alloc符号断点 到libobjc.A.dylib`+[NSObject alloc]:

3.看汇编:debug->Always Show Disassemby 到libobjc.A.dylib`objc_alloc:

4.直接在源码里面跑,最方便但是需要配置,opensource.apple.com/tarballs/ob… 开源了objc源码,alloc源码就在里面

1.开始调试

因为x0-x7 函数参数,x0存返回对象,所以用register read x0读取下x0

alloc执行前
alloc执行后

alloc 执行后,x0存了对象的指针,说明alloc的确具备开辟内存空间的能力,但是具体怎么开辟的,又是分配多大到内存空间呢,一步步点击后,来到instanceSize这个方法

2.内存对齐

先看个简单的题目,如图

内存对齐

上述代码打印出来的结果为:24,16。

为什么结构体内是相同的结构,系统却分配了不同大小的内存呢,这就是系统根据"内存对齐"规则来分配内存的结果。

简单里说,这里StructOne的a在分配的时候,因为无法和后面8字节的b分配在一个地址上,所以需要单独分配8字节给a存储,c和d的长度可以同时存在一个地址上,所以StructOne的大小就是 8 + 8 + 8 = 24;StructTwo的b因为8字节可以独享一个地址,a,d,c三者的长度可以同时存在一个地址上,所以StructTwo的大小就是8 + 8 = 16。当然内存对齐的规则不止这么简单,具体规则如下:

1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。

2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。


好了,了解了内存对齐,来看下系统的做法。

对象申请的内存大小
8字节对齐

看到instanceSize 这个方法,这就是分配内存空间大小的方法,点击进去,会看到这里做了一次内存对齐的操作,具体算法如下:(x + 7) & ~7,因为对象开辟空间时候,会自带8字节的isa指针,所以这里的x最小为8。

(x + 7) & ~7,即为 15 & ~7。

0000 1111 //15

1111 1000 //~7

&运算后,即为8的倍数,所对象申请的内存大小为8的倍数,且最小为16,因为

if(size <16) size =16;复制代码

说明:8字节对齐,cpu读取数据时不断变化读取的长度是影响速度的,所以为了加快cpu的读取,可以定个固定的值,用空间换时间,因为8字节的数据类型较多,所以以8字节对其进行计算


那么系统开辟的内存大小就是等于对象申请的内存大小嘛,其实不一定的,dyld在register_notification映射文件加载时,是加载类的结构,类的结构包含整个data数据段,data里面有个ro,ro里面存着ivar_list,protocol_list等等,拿到类信息,进行字节对齐,那么还是8字节对齐嘛

系统开辟的内存大小
宏定义的值

假设,这里对象包含5个8字节的属性,那这里传进来的sise就是40,那上面的算法

k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
slot_bytes = k << SHIFT_NANO_QUANTUM;复制代码

即为 slot_bytes = (40 + 16 - 1) >> 4 <<4,这里采用先右移4位,在左移4位的算法来进行16字节对齐,因此当对象申请的内存大小为40时,系统开辟的内存大小却是以16字节对齐,需要补齐为48。

总结:对象申请的内存大小 和 系统开辟的内存大小是不一定相同的

8字节对齐 -- 对象里面的属性 16字节对齐 -- 对象本身

这样设计的原因是,如果只是给这个对象刚刚好的大小,可能存在某一刻溢出的情况,需要留点阈值

3.初始化isa

内存分配完之后,来到

obj->initInstanceIsa(cls, dtor);复制代码

 它做的事就是把对象和类通过isa的方式进行绑定,isa中包含大量的类信息,这就相当于给开辟的这片空间指定一个拥有者。

4.init底层

alloc大概就是做了这些,那init呢,来看看init的底层。

init底层源码

init的底层源码就是如此简单,它直接返回了这个对象,什么也没有做。理论上,init是无用的代码可以不写,但是平常出于开发习惯和规范还是要体现的,并且init是算一种工厂设计,是交给子类取自定义重写用的。


总结:
alloc:alloc通过内存对齐的方式在内存中开辟申请了空间,伴随着初始化了isa。相当于建了一所固定大小的房子,并且指明了房子的所有者。
init:init直接返回了对象本身,没有任何额外的操作。正是因为它什么也不做,所以对开发者更友好,可以任意重写,提供给开发者一个初始化的接口。相当于让开发者尽情的在房子里面做精装修。