前言
上篇文章我们初步分析了类的结构,在类的创建过程中我们看到这一行代码: initInstanceIsa绑定和initIsa创建.那么这个isa是什么,它的结构又是什么,它的作用又是什么?下面具体分析下这个有意思的isa
一.初始isa
isa 就是一个Class类型的指针,每个对象都有一个isa指针,我们打开源码:
我们断点跟进下进入initIsa方法中
进入isa_t
我们可以看到这个一个结构 union 的类型
知识点补充:
1.结构体
自定义数据类型,一些类型的集合组成一个类型。
结构体的定义:
struct 结构体名 {成员1,成员2,...};
2.union 联合体(共用体)
“联合”是一种特殊的类,也是一种构造类型的数据结构。
在一个“联合”内可以定义多种不同的数据类型,一个被说明为该“联合”类型的变量中,
允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的
(还有一个节省空间的类型:位域)。
参考资料:
进入isa_t的ISA_BITFIELD位域中我们可以看到下图的结构:
我们以ios的__arm64__环境为例,具体分析下每个属性字段代表的含义:
* **`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;下一行,表示为isa的bits成员赋值,重新执行lldb命令p newisa,得到的结果如下
其中magic是59是由于将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宏定义的值为0x0000000ffffffff8ULLx86_64中,ISA_MASK宏定义的值为0x00007ffffffffff8ULL
【方式三】
通过查看object_getClass的源码实现,同样可以验证isa与类关联的原理,有以下几步:
-
main中导入#import <objc/runtime.h>
-
通过
runtime的api,即object_getClass函数获取类信息 -
查看
object_getClass函数源码进入object_getClass底层实现点击getIsa的方法,点击ISA(),进入源码,可以看到如果是indexed类型,执行if流程,反之 执行的是else流程 -
在
else流程中,拿到isa的bits这个位,再& ISA_MASK,这与方式二中的原理是一致的
【方式四】
至此四种方法完美验证了**isa**是通过**ias_t**中的**shiftcls**这个属性存储了类信息通过 **initInstanceIsa**方法把isa和cls之间进行了绑定.
具体的计算流程如下图所示:
四.总结
以上是isa的探索过程,包括了isa的结构分析,位域字段含义,存储内存空间以及如何和cls关联的.并通过了四种方法进行验证.
学习是通过积累的过程,不一定要做的尽善尽美,能使自己进步一点点就是最大的乐趣,加油!!!!!