iOS开发过程中,最常用的代码就是我们每天都在写的 [[xxx alloc] init],那么苹果底层,alloc和init分别执行了什么呢?下面我们来一探究竟。
alloc
可以清晰的看到,alloc可以为LGPerson开辟内存地址,并且p1和p2指向了同一个地址。
先放一个流程图
不亲自验证不是个好的coder,来,开干
接着我们查看一下汇编,Debug->Debug Workflow->Allows Show Disassembly
可以看到调用了系统的objc_alloc方法
查看一下源码
添加符号断点alloc,继续执行
找到方法_objc_rootAlloc
查看一下源代码
添加符号断点_objc_rootAlloc,继续执行
找到方法_objc_rootAllocWithZone
查看一下源代码
符号断点_objc_rootAllocWithZone,继续
终于看到了retq了,可以看看是不是alloc好了
汇编语法查看一下
验证成功,终于得到了我们类的地址了,再回看源码,发现了一个有意思的事情
最终箭头函数_objc_rootAllocWithZone成功alloc是通过callAlloc方法里返回的,并且_objc_rootAlloc,_objc_rootAlloc都调用了callAlloc,但是汇编里我们始终没有看到callAlloc啊,这又是为什么呢?
这就是苹果帮我们做的编译器优化,里面知识我也不太懂,感兴趣的同学可以自行研究一下。
init和new
同理,我们可以快速的查到init和new的底层代码
从源码我们可以看到,init方法并没有做任何操作,而new方法其实本质上就是alloc + init,那么init方法没有做任何操作为什么还这样设计呢,其实苹果这样做的原因就是让我们可以重写init来初始化一些我们自己的操作,比如初始化UI,初始化变量等等,具体可以参照工厂模式
内存申请
接下来我们看一下内存申请,先上图
我们去找到cls->instanceSize核心方法
点击进入方法内部发现有这样一句代码if (size < 16) size = 16; 也就是说通过allo或者new创建出来的对象最小的大小是16个字节,我们再看一下字节对齐算法
写的是真的讲究 这里又产生了一个疑问,在计算对象需要的内存使用的是8字节对齐,实际分配内存使用的是16字节对齐。此时现在有一个疑问,对象的内存分配系统为什么要这样处理呢,这就涉及了对象的本质。
对象的本质
首先我们创建一个mac app项目,在main文件下创建一个这样一个类
然后打开终端进入当前项目下输入clang -rewrite-objc main.m 指令,这样我们在文件夹下就得到了一个.cpp的文件,打开.cpp文件搜索LGPerson我们可以看到
此时发现对象的本质是一个objc_object的结构体,其结构体内存储的是 isa指针 + 成员变量的值。 关于
1、那么为什么要字节对齐是因为字节是内存的容量单位。但是,CPU在读取内存的时候,却不是以字节为单位来读取的,⽽是以“块”为单位读取的,所以⼤家也经常听到⼀块内存,“块”的⼤⼩也就是内存存取的⼒度。如果不对⻬的话,在我们频繁的存取内存的时候,CPU就需要花费⼤量的精⼒去分辨你要读取多少字节,这就会造成CPU的效率低下,如果想要CPU能够⾼效读取数据,那就需要找⼀个规范,这个规范就是字节对⻬。
2、为什么对象内部的成员变量是以8字节对⻬,系统实际分配的内存以16字节对⻬? 以空间换时间。苹果系统采取16字节对⻬,这样分配是因为OC的对象中,第⼀位叫isa指针,它是必然存在的,⽽且它就占了8位字节,就算对象中没有其他的属性了,也⼀定有⼀个isa,那对象就⾄少要占⽤8位字节。如果以8位字节对⻬的话,如果连续的两块内存都是没有属性的对象,那么它们的内存空间就会完全的挨在⼀起,是容易混乱的。以16字节为⼀块,这就保证了CPU在读取的时候,按照块读取就可以,效率更⾼,同时还不容易混乱。
结构体对齐方式
1:数据成员对齐规则:结构(struct)的第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。
3:收尾工作:结构体的总大小,也就是sizeof的结果必须是其内部最大成员的整数倍,不足的要补齐。
举个小例子
验证一下