相信大多数iOS开发者们每天都是沉浸在项目业务逻辑代码编写和完善当中,由于底层学习对本身业务逻辑没太多的关联,所以对底层知识知之甚少,也就导致我们iOS开发者基本到达一个就业瓶颈,高不成低不就,因此学习底层知识是你挑战高薪,站在iOS开发者顶端的必经之路;我们不仅要会业务逻辑代码的编写,更要对其底层编译过程原理的精通,所以让我们一起踏上底层学习之路。
PS:底层学习认准逻辑,认准酷c,“kc:靓仔,嗯!奇奇怪怪。。。”
在开发当中,我们创建一个对象的时候,通常都是*[[LhkhPerson alloc]init]*
那有没有想过为什么要这么做呢?alloc到底干了些什么呢?
alloc的作用
带着这个疑问我们今天就一起来探究一下对象初始化时alloc都干了些啥。。。
首先我们来看一个demo实例:
我们这边定义了一个LhkhPerson类,然后通过alloc了一个对象p,又通过init了两个对象p1,p2,那么这三者的关系是什么呢?接下来我们通过运行看一下打印结果:
打印结果分为两块,可以是下面这个图解
- 从第一块我们可以看出来它们三个内容和地址是一模模一样样,当类alloc创建对象的时候开辟了一块内存空间,三个对象都指向这块地址内存空间;
- 从第二块我们可以看出来,当对象init过后实质上并没有开辟新的内存空间,只是在栈区有了自己的指针地址,而且它们是连续的。
知道了alloc干了什么后,那么我们就想,alloc到底是怎么实现的呢,内部到底有个什么流程呢,那接下来我们就再次探究一下alloc流程:
当我们点击查看alloc的实现源码时
没有实现的源码,这个时候我们就相当苦涩了啊😭,不慌,查看底层alloc流程我们有很多的方法,以下我们就一起来看看:
方式一:通过符号短点
方法二:汇编跟流程 然后确认调用符号,再次结合符合断点跟进
方法三:通过符号断点,断住关键位置alloc
方法...
探索底层源码方法有很多,但是我们应该会发现以上这些方式探索起来还是有点繁琐,那有没有啥简单方法呢?那肯定是有的啊,通过直接编译底层源码来实现。
在上面探索时我们可以发现其实objc_alloc其实是在libobjc库中的,那我们通过苹果官方找到objc4可以看到当前源码最新为824,那我们采用前一个版本818.2,开车开车;
ps:818.2版本底层源码编译能够成功请参考酷c的github配置。
alloc底层探索
上图:
我们通过编译objc源码,断住调用alloc的地方
接下来就进入到主题了
- 我们按住command点击
alloc
方法,然后跳转到NSObject.mm(.mm为c++文件)文件中的+ (id)alloc{}方法,可以看到调用了一个中间方法_objc_rootAlloc,并带上当前类作为参数
2. command点击_objc_rootAlloc
继续跟进,可以看到调用中间方法
callAlloc
3. command点击callAlloc
继续跟进,并带上三个参数,cls(当前类),checkNil(是否需要判空直接传的是 false ,站在系统角度,前面已经调用过 callAlloc 的时候进行了判空了,没必要再次进行判空的了),allocWithZone(直接传入的true,关于allocWithZone这个方法,查看apple官方文档可以理解为何alloc方法是同样的意思,只不过由于历史原因不用了)
核心方法,首先判断__OBJC2__是不是objc2.0,如果是objc2.0那么会接着进入两个宏:
- slowpath:告诉编译器,传入的条件结果为假的可能性很大
- fastpath:告诉编译器,传入的条件结果为真的可能性很大
然后进入到_objc_rootAllocWithZone方法中
4.command 点击 _objc_rootAllocWithZone
跳转到这个方法当中去
5.然后command 点击_class_createInstanceFromZone方法
通过分析我们可以发现里面有三个比较核心的方法
instanceSize
计算需要开辟的内存大小calloc
开辟内存空间initInstanceIsa
将obj的isa与类关联起来 接下来我们具体来看看这撒个方法:
- instanceSize开辟内存大小
那是怎么计算内存大小呢?一般我们对象的内存大小有哪些东西决定呢?(对象中一般可能包含属性,成员变量,方法,协议,isa等,那决定对象的内存大小的是成员变量,跟其他没有关系)
刚开始LhkhPerson没有实例变量的时候,我们打印其大小时可以看到输出8,那么这个8是怎么来的呢?因为LhkhPerson是继承于NSObject,而NSObject里面有一个Class isa,这个Class是一个结构体指针类型,所以为8.
- 那我们知道了8个字节大小来自于继承于NSObject;
- 我们跟进去到
instanceSize
方法中时发现,if(size<16) size = 16;
我们可以得到最少为16,不满16直接为16; PS:字节对齐算法我们可以看到有个字节对齐的方法word_align
,点进去看看有一个(x + WORD_MASK) & ~WORD_MASK
这个算法,那我们来计算一下试试: 我们对象没有成员变量的时候调用这个unalignedInstanceSize
返回的是8,然后WORD_MASK
等于7,那么我们算法式子为(8 + 7)& ~7(也就是15与上7的取反 (~为取反符号))
15 0000 1111
7 0000 0111
~7 1111 1000
15 & ~7
0000 1111
& 1111 1000
0000 1000 --->8
可以看成8 >>3 <<3
0000 1000 >>3 0000 0001
0000 0001 <<3 0000 1000
所以可以看出为8字节对齐,取8的整数倍数,不足8的抹去;
4. 接着往下看,我们跟进fastInstanceSize
方法中去发现里面有一个align16(size + extra - FAST_CACHE_ALLOC_DELTA16)
算法,那我们继续跟进align16
方法中
按照上面字节对齐方法我们可以发现其实也就是16字节对齐方式,取16的整数倍数,不足16的抹去;那为什么要字节对齐呢?又为什么以16字节对齐呢?
舍空间换时间
-
我们知道CPU是以字节块为单位读取,如果我们没有按照字节对齐方案进行读取的话会拖累CPU的运行效率;
-
那为什么是16字节对齐呢,因为我们对象本身isa就占用了8个字节,如果都是以8字节的话,那每个对象的isa会出现紧挨着,容易造成访问混乱;也不适合更大的数对齐,毕竟如果一个对象真的只有一个isa属性的话,我们来个32字节那也不合适啊,浪费内存。
-
创建obj和开辟内存空间
- 将obj的isa与类关联起来 刚开始创建obj的时候,我们p一下obj得到一个地址,这边注意我们只是创建了obj但是没分配实际的内存空间大小,这个是脏内存地址
然后我们继续下一步走进到calloc
方法时,我们再下一步
但是这个时候你会发现当前obj还是id类型,那怎么跟我们的LhkhPerson关联呢,接着走
最后在return出去这个obj即一个对象alloc完成。
总结
alloc主要就是计算内存大小,开辟内存空间,isa关联类。
最后有什么错误之处请指出啊,能力有限,相互学习,大神勿喷!