iOS 对象原理探索四-isa结构分析

707 阅读6分钟

ios对象原理探索三

前言

上篇文章我们初步分析了类的结构,在类的创建过程中我们看到这一行代码: initInstanceIsa绑定和initIsa创建.那么这个isa是什么,它的结构又是什么,它的作用又是什么?下面具体分析下这个有意思的isa

一.初始isa

isa 就是一个Class类型的指针,每个对象都有一个isa指针,我们打开源码:

我们断点跟进下进入initIsa方法中

进入isa_t

我们可以看到这个一个结构 union 的类型

知识点补充:
    1.结构体
        自定义数据类型,一些类型的集合组成一个类型。
        结构体的定义:
        struct 结构体名 {成员1,成员2,...};
    2.union 联合体(共用体)
        “联合”是一种特殊的类,也是一种构造类型的数据结构。
        在一个“联合”内可以定义多种不同的数据类型,一个被说明为该“联合”类型的变量中,
        允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的
        (还有一个节省空间的类型:位域)。

参考资料:

结构体:《结构体详解》, union:《联合体详解

进入isa_tISA_BITFIELD位域中我们可以看到下图的结构:

我们以ios的__arm64__环境为例,具体分析下每个属性字段代表的含义:

* **`nonpointer`**(存储在第0字节) : 是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa。
*   **`has_assoc`** (存储在第1个字节): 关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存(dealloc的底层代码有体现)。

*   **`has_cxx_dtor`**(存储在第2个字节): 析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象(dealloc的底层代码有体现)。

*   **`shiftcls`** :(存储在第3-35字节)存储类的指针,其实就是优化之前 isa 指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。

*   **`magic`**(存储在第36-41字节):判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。

*   **`weakly_referenced`**(存储在第42字节):对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放(dealloc的底层代码有体现)。

*   **`deallocating`**(存储在第43字节):标志对象是否正在释放内存。

*   **`has_sidetable_rc`**(存储在第44字节):判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

extra_rc(存储在第45-63字节。):存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc 的值就为 9。

具体的用图来表示isa的存储情况:

二.验证

  • LLDB调试

断点中nonpointer返回为flase,根据定义说明他是一个纯指向类或元类的指针.继续断点跟进当nonpointer为ture时,跟进newisa位域值:

说明:
newisa.bits = ISA_MAGIC_VALUE;
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL

结果如下图所示:

继续执行,走到newisa.bits = ISA_MAGIC_VALUE;下一行,表示为isabits成员赋值,重新执行lldb命令p newisa,得到的结果如下

其中magic59是由于将isa指针地址转换为二进制,从47(因为前面有4个位域,共占用47位,地址是从0开始)位开始读取6位,再转换为十进制,如下图所示

上面分析了这么多isa,其中包括它的定义,位域中每个字段代表的含义,内存的存储空间,那它到底和类如何关联起来的?

三.isa和class关联

上述从**isa**定义中看到了**ias_t**中有**shiftcls**这个属性的解释,它存储了类信息通过 **initInstanceIsa**方法把isa和cls之间进行了绑定,以下方法我们可以验证其过程:

  • 【方式一】通过initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;验证

  • 【方式二】通过isa指针地址ISA_MSAK 的值 & 来验证

  • 【方式三】通过runtime的方法object_getClass验证

  • 【方式四】位运算验证

【方式一】

我们用源代码在这两行代码加入断点。确保调用传递进来的cls是我们要研究的MRPseron类运行至此时。在lldb做以下操作

断点**LLDB**打印执行**p newisa**

cls默认值,变成了LGPerson,将isa与cls完美关联

【方式二】

回到**_class_createInstanceFromZone**方法,此时**cls 与 isa已经关联完成**,执行**po objc**

执行**x/4gx obj**,得到isa指针的地址**0x001d8001000020e9,再将**isa**指针地址 & **ISA_MASK** (处于**macOS**,使用**x86_64**中的宏定义),即 po **0x001d8001000020e9**& 0x00007ffffffffff8ULL **得出**MRPerson**

  • arm64中,ISA_MASK 宏定义的值为 0x0000000ffffffff8ULL
  • x86_64中,ISA_MASK 宏定义的值为 0x00007ffffffffff8ULL

【方式三】

通过查看object_getClass的源码实现,同样可以验证isa与类关联的原理,有以下几步:

  1. main中导入#import <objc/runtime.h>

  2. 通过runtime的api,即object_getClass函数获取类信息

  3. 查看object_getClass函数源码进入object_getClass 底层实现点击getIsa的方法,点击ISA(),进入源码,可以看到如果是indexed类型,执行if流程,反之 执行的是else流程

  4. else流程中,拿到isabits这个位,再 & ISA_MASK,这与方式二中的原理是一致的

【方式四】

至此四种方法完美验证了**isa**是通过**ias_t**中的**shiftcls**这个属性存储了类信息通过 **initInstanceIsa**方法把isa和cls之间进行了绑定.

具体的计算流程如下图所示:

四.总结

以上是isa的探索过程,包括了isa的结构分析,位域字段含义,存储内存空间以及如何和cls关联的.并通过了四种方法进行验证.

学习是通过积累的过程,不一定要做的尽善尽美,能使自己进步一点点就是最大的乐趣,加油!!!!!