OC底层学习---对象alloc初探

891 阅读6分钟

相信大多数iOS开发者们每天都是沉浸在项目业务逻辑代码编写和完善当中,由于底层学习对本身业务逻辑没太多的关联,所以对底层知识知之甚少,也就导致我们iOS开发者基本到达一个就业瓶颈,高不成低不就,因此学习底层知识是你挑战高薪,站在iOS开发者顶端的必经之路;我们不仅要会业务逻辑代码的编写,更要对其底层编译过程原理的精通,所以让我们一起踏上底层学习之路。

PS:底层学习认准逻辑,认准酷c,“kc:靓仔,嗯!奇奇怪怪。。。”

在开发当中,我们创建一个对象的时候,通常都是*[[LhkhPerson alloc]init]*

那有没有想过为什么要这么做呢?alloc到底干了些什么呢?

alloc的作用

带着这个疑问我们今天就一起来探究一下对象初始化时alloc都干了些啥。。。

首先我们来看一个demo实例:

截屏2021-06-26 下午6.08.01.png 我们这边定义了一个LhkhPerson类,然后通过alloc了一个对象p,又通过init了两个对象p1,p2,那么这三者的关系是什么呢?接下来我们通过运行看一下打印结果:

截屏2021-06-26 下午6.20.54.png

打印结果分为两块,可以是下面这个图解

alloc探究图.png

  • 从第一块我们可以看出来它们三个内容和地址是一模模一样样,当类alloc创建对象的时候开辟了一块内存空间,三个对象都指向这块地址内存空间;
  • 从第二块我们可以看出来,当对象init过后实质上并没有开辟新的内存空间,只是在栈区有了自己的指针地址,而且它们是连续的。

知道了alloc干了什么后,那么我们就想,alloc到底是怎么实现的呢,内部到底有个什么流程呢,那接下来我们就再次探究一下alloc流程:

当我们点击查看alloc的实现源码时

截屏2021-06-27 上午10.14.05.png 没有实现的源码,这个时候我们就相当苦涩了啊😭,不慌,查看底层alloc流程我们有很多的方法,以下我们就一起来看看:

方式一:通过符号短点

截屏2021-06-27 上午10.35.48.png

截屏2021-06-27 上午10.39.21.png

截屏2021-06-27 上午10.42.19.png

截屏2021-06-27 上午10.44.26.png

截屏2021-06-27 上午10.44.51.png 方法二:汇编跟流程 然后确认调用符号,再次结合符合断点跟进

截屏2021-06-27 下午12.10.52.png

截屏2021-06-27 下午12.11.32.png

方法三:通过符号断点,断住关键位置alloc

截屏2021-06-27 下午12.25.49.png

截屏2021-06-27 下午12.27.00.png 方法...

探索底层源码方法有很多,但是我们应该会发现以上这些方式探索起来还是有点繁琐,那有没有啥简单方法呢?那肯定是有的啊,通过直接编译底层源码来实现。

在上面探索时我们可以发现其实objc_alloc其实是在libobjc库中的,那我们通过苹果官方找到objc4可以看到当前源码最新为824,那我们采用前一个版本818.2,开车开车;

ps:818.2版本底层源码编译能够成功请参考酷c的github配置。

alloc底层探索

上图:

alloc底层流程.png

我们通过编译objc源码,断住调用alloc的地方

截屏2021-07-10 下午3.59.54.png

接下来就进入到主题了

  1. 我们按住command点击alloc方法,然后跳转到NSObject.mm(.mm为c++文件)文件中的+ (id)alloc{}方法,可以看到调用了一个中间方法_objc_rootAlloc,并带上当前类作为参数

iShot2021-07-10 16.09.39.png 2. command点击_objc_rootAlloc继续跟进,可以看到调用中间方法 callAlloc image.png 3. command点击callAlloc继续跟进,并带上三个参数,cls(当前类),checkNil(是否需要判空直接传的是 false ,站在系统角度,前面已经调用过 callAlloc 的时候进行了判空了,没必要再次进行判空的了),allocWithZone(直接传入的true,关于allocWithZone这个方法,查看apple官方文档可以理解为何alloc方法是同样的意思,只不过由于历史原因不用了)

image.png 核心方法,首先判断__OBJC2__是不是objc2.0,如果是objc2.0那么会接着进入两个宏:

  • slowpath:告诉编译器,传入的条件结果为假的可能性很大
  • fastpath:告诉编译器,传入的条件结果为真的可能性很大

然后进入到_objc_rootAllocWithZone方法中

4.command 点击 _objc_rootAllocWithZone跳转到这个方法当中去

image.png

5.然后command 点击_class_createInstanceFromZone方法

image.png iShot2021-07-11 13.38.24.png 通过分析我们可以发现里面有三个比较核心的方法

  1. instanceSize 计算需要开辟的内存大小
  2. calloc 开辟内存空间
  3. initInstanceIsa 将obj的isa与类关联起来 接下来我们具体来看看这撒个方法:
  • instanceSize开辟内存大小

image.png

那是怎么计算内存大小呢?一般我们对象的内存大小有哪些东西决定呢?(对象中一般可能包含属性,成员变量,方法,协议,isa等,那决定对象的内存大小的是成员变量,跟其他没有关系)

image.png

image.png

image.png

刚开始LhkhPerson没有实例变量的时候,我们打印其大小时可以看到输出8,那么这个8是怎么来的呢?因为LhkhPerson是继承于NSObject,而NSObject里面有一个Class isa,这个Class是一个结构体指针类型,所以为8. image.png

image.png

image.png

  1. 那我们知道了8个字节大小来自于继承于NSObject;
  2. 我们跟进去到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的抹去;

image.png

image.png 4. 接着往下看,我们跟进fastInstanceSize方法中去发现里面有一个align16(size + extra - FAST_CACHE_ALLOC_DELTA16)算法,那我们继续跟进align16方法中

image.png 按照上面字节对齐方法我们可以发现其实也就是16字节对齐方式,取16的整数倍数,不足16的抹去;那为什么要字节对齐呢?又为什么以16字节对齐呢?

舍空间换时间

  • 我们知道CPU是以字节块为单位读取,如果我们没有按照字节对齐方案进行读取的话会拖累CPU的运行效率;

  • 那为什么是16字节对齐呢,因为我们对象本身isa就占用了8个字节,如果都是以8字节的话,那每个对象的isa会出现紧挨着,容易造成访问混乱;也不适合更大的数对齐,毕竟如果一个对象真的只有一个isa属性的话,我们来个32字节那也不合适啊,浪费内存。

  • 创建obj和开辟内存空间

image.png

  • 将obj的isa与类关联起来 刚开始创建obj的时候,我们p一下obj得到一个地址,这边注意我们只是创建了obj但是没分配实际的内存空间大小,这个是脏内存地址 image.png

然后我们继续下一步走进到calloc方法时,我们再下一步

image.png 但是这个时候你会发现当前obj还是id类型,那怎么跟我们的LhkhPerson关联呢,接着走

image.png 最后在return出去这个obj即一个对象alloc完成。

总结

alloc主要就是计算内存大小,开辟内存空间,isa关联类。

最后有什么错误之处请指出啊,能力有限,相互学习,大神勿喷!