iOS对象的底层探索(下)

1,057 阅读6分钟

在上篇文章我们讨论了对象的创建过程,以及对象的内存开辟、内存对齐规则,接下来我们来继续研究对象的内存,本文将从一下几个方面来展开

1. 影响对象内存的因素

2. 对象的内存分析

3. 联合体和位域

4、nonPointerisa

5、如何利用isa的位运算得到类对象

6、new方法

一、影响对象内存的因素

首先我们还是在我我们的LSPerson里面增加一下属性和方法: image.png

我们按照 上一篇文章内存对齐规则(对象内部以8字节对齐,系统实际分类内存以16字节对齐)知道 对象内部需要 40个字节(包含了属性+isa指针,实际38,但是8字节对齐所以40), 系统实际开辟48个字节(16字节对齐所以最小是48),我们通过系统提供的方法验证一下 image.png

   我们在上一篇文章中struct结构体计算中发现,成员的位置会影响结构体内存的大小,但是在对象中我们发现
   好像并没有(可以通过调整属性顺序来验证)因为位置不同,影响对象开辟的内存大小,是因为系统对属性进行了
   重排,达到内存优化

同时还得出一个结论:系统实际分配给对象的内存大小不仅和类对象中添加的方法无关,同时也与成员变量(属性)的顺序无关

注意:

我们上面验证了对象的开辟内存大小跟属性问题是没有关系的,那么成员变量了也是一样吗? image.png image.png image.png 结论:通过上面的几段段吗我们可以发现,系统并不会对实例变量进行重排

二、对象的内存分析

在上面我们说系统会对属性进行重排,那么我们看一下系统是如何排列的,我们通过LLDB进行调试 输入 x/6gx

image.png

注意:我们通过x/6gx 得到对象的内存地址,通过分析我们发现age+number被系统重排之后放在一一个内存块里面。并且
对象里面地址存储的是值。

通过上面我们知道:iOS对象的内存大小和属性以及属性的赋值顺序无关,系统会自动重排对象属性的顺序,来进行内存优化,并且对象里面存储的是属性值。对象里面存储的是属性值,并不是属性。

三、位域和联合体

1、什么是位域

位域声明:有些信息在存储是,并不需要占用一个完整的字节,而只需要占几个或一个二进制位。例如在存放一个开关变量时,只有0和1两种状态,用1位二进制位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。(比如char类型的需要1个字节,也就是8位,如果我们存储的是一个开关变量,也有0和1,这个是用8位就会浪费,为了节省空间我们就可以使用1位二进制表示来节省存储空间) 所谓“位域”是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每一个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。 image.png 一般在开发中我们不会这么去使用,只有系统级别的才会去这么操作。

2、什么是联合体(union)

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

image.png 通过上面我们发现 union里面 对象是互斥的,也就是同一时刻里面只会有一个成员带有值。那么联合体的作用是啥了? image.png 因为联合体是互斥的,所以可以节省内存空间,联合体必须要能肉容纳最大的成员变量,计算出来的大小必须是其最大成员变量(基本数据类型)的整数倍。

    结构体和联合体的区别:
    结构体:成员变量的值能够共存,内存开辟必须粗放
    联合体:成员变量的值互斥, 节省内存空间

四、nonPointerisa

在上一遍文章iOS对象的底层探索(上)中我们通过objc源码分析了_class_createInstanceFromZone函数知道了对象所需要的内存大小如何计算,以及系统开辟对象内存大小的规则,然后在这个方法返回了ojb,也就是我们需要的实例对象。我们继续分析两者是如何联上的。

image.png 通过上面的断点,我们知道执行了方法initInstanceIsa(或者initIsa)之后,obj就和LSPerson类进行绑定了。我们继续看一下这个函数 image.png 这个函数里面有一个isa_t的isa,最后isa = newisa, 通过源码知道 isa_t 是一个联合体。isa指针是一个class类型的结构体指针,主要用来存储内存地址。isa指针有8个字节即64位。以前版本均为Class类型的结构体指针,而当前普遍使用nonPointerIsa,为了兼容性所有isa_t 采用联合体类型(这也是联合体的优势). image.png

五、如何用isa的位运算得到类对象

当前都是nonPointerIsa, isa指针存储的内容保存到了名为ISA_BITFIELD的结构体中,代码截图如下:

image.png

    NONPOINTER_ISA 存储的内容
    1、nonpointer: 表示是否对isa指针开启指针优化,0:纯isa指针,1:不止是类对象地址,isa包含了类信息、
      对象的引用记数。
    2、has_assoc: 关联对象标志位,0没有,1存在。
    3、has_cxx_dtor: 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,
      则可以更快的释放对象。
    4、shiftcls: 存储类指针的值。开启指针优化的情况下,在arm64架构中(真机)由33位用来存储类指针。
    5、magic: 用户调试器判断当前对象是真的对象还是没有初始化的空间
    6、weakly_referenced: 标志对象是否被指向或者曾指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
    7、deallocationg: 标志对象是否正在释放内存。
    8、has_sidetable_rc: 当对象引用计数extra_rc所能存储的最大范围时,则需要借用该变量存储进位。
    9、extra_rc: 当表示该对象的引用计数值,当对象的引用计数超过最大的存储范围时,则需要使用到上面的
       has_sidetable_rc.

在M1架构中,将isa指针内存地址右移3位(清除低3位),左移12位,再右移9位,得到的内存地址即为类对象的内存地址,如下:

image.png 扩展也可以通过 & ISA_MASK 来得到类对象的地址

image.png image.png

六、new方法

通过源码分析,发现和alloc一样也是会之行callAlloc 和init方法 image.png