isa是什么

之前我们看过,每个继承自NSObject类的子类,都有一个藏在第一个位置的隐藏属性,就是isa指针。看个图:
屏幕快照 2020-03-20 11.00.41.png
这个Class,点进去看其实就是 typedef struct objc_class *Class; 看上去感觉isa就是查找类的指针喽。其实,isa这家伙不简单啊,isa是一个联合体的东西,看下源码:
屏幕快照 2020-03-20 18.07.12.png

Tpis:为什么isa明明是个联合体,却在类中作为属性的时候确是Class类型呢? 在以前我们了解的都是isa就仅仅是指向类的指针,但是后来苹果对isa这个做了一个优化,使得它还拥有了一些其他的数据存储,至于还继续用Class类型,你可以认为是遗留下来的习惯啊这些。在底层真正getIsa()的时候,也会对isa.bits进行Class类型的强转。

其中isa_t的两个元素Classuintptr_t的数据类型,如下:
屏幕快照 2020-03-20 18.06.10.png
屏幕快照 2020-03-20 18.05.55.png
ISA_BITFIELD根据不同的架构,表示的位域也不一样,看下图
屏幕快照 2020-03-20 18.07.27.png
好,看了源码后继续聊:
union联合体是什么鬼,简单理解就是一个共用一片内存的共用体,给一个元素赋值的时候会把另一个覆盖掉,所占用的内存就是最大元素的所需空间。采用这种方式能节省空间又加速运算速度。isa_t中有Class cls; uintptr_t bits ; Class是一个objc_class*指针8字节,uintptr_t就是typedef unsigned long uintptr_t;也是8字节,所以这个联合体,占用的内存空间就是8字节64位,接下来ISA_BITFIELD,就站出来说明一下这64位的位置分别代表什么含义,看一下:

nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中还包含了类信息、对象的引用计数等; has_assoc:关联对象标志位,0没有,1存在; has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象; shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。在x86_64 架构中有44位来存储类指针; magic:用于调试器判断当前对象是真的对象还是没有初始化的空间 weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。 deallocating:标志对象是否正在释放内存 has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位 extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到 has_sidetable_rc。

曾经我们以为isa就是单纯的指向类,元类等等的指针,没想到这小小的isa_t中蕴含着这么多内容。在arm64x86_64这架构中,都有shiftcls来存储类指针的值,显然,我们要是想知道这个isa指向什么,就要读到这个数据,于是乎,往上看,在不同架构中,都存在ISA_MASK这个定义,这个什么?可以理解为,蒙版(面具),给isa.bits带上这个面具,就能知道这个isa,究竟指向什么,请看:
屏幕快照 2020-03-22 15.46.48.png
newisa就是我们在创建Person对象时创建的isalldb调试打印 po newisa.bits & 0x00007ffffffffff8 得出的就是shiftcls的数据,正是Person0x00007ffffffffff8 就是上边提到的蒙版。

isa的指向流程

先上一个苹果官方的图:
1577119172037-79242bfa-1f35-42a1-959c-efe437d98261.png
乍一看,什么鬼。乱七八糟的。别慌,拆开看,这里我们就看虚线箭头的走位,用Person类来讲解一下,结合上班我们isa.bits加蒙版的小技能。
Person继承自NSObject,继承来的光环isa,必然在属性的第一个位置,断点打起来,x/4gx (以16进制输出4段,每段8个字节)打印一下内存:
屏幕快照 2020-03-22 15.56.55.png
然后的操作有点骚,我分为1、2、3、4、5(这是写着写着补充的)来分开讲:
屏幕快照 2020-03-22 16.04.12.png
屏幕快照 2020-03-22 16.46.39.png
1、person是指针类型没啥问题,指向Person的实例对象,x/4gx 把对象打印出来,第一段8字节,就是isa指针,直接拿到这个指针&(“与”操作)蒙版 0x00007ffffffffff8 直接得出 Person 类;
2、上面拿到了person对象的isa,通过这个isa,用p打印p 0x001d800100001315 & 0x00007ffffffffff8 得到的$2,$2可以理解他指向元类的实例对象,,把这个$2指向的对象打印四段,获取他的isa,加蒙版,得出还是Person,此时的Person 就是“元类”;
3、得出元类了,然后通过p打印得到$4,可以理解指向根元类对象,然后用他的isa,加蒙版,打印得出NSObject,他就说传说中的“根元类”;那么还有没有下一层,继续往下p;
4:继续p得到了$6,他应该就指向根根元类了吧,用他的isa加上蒙版发现,还是NSObject。
5:写着写着我发现,光看名字是NSObject,也不能说找到的就是自己啊,不严谨,好,拿到$6相关的数据继续往下找,打印$8,发现了啥没,连地址都一样了,$6和$8打印的起始地址都是0x100afe0f0,这下确定是自己了吧?就这么骚。
所以结合官方的图,总结一下:对象(person)—>类(Person)—>元类(Person)—>根元类(NSObject)—>根元类(NSObject)。

tips:看上边的图,发现Subclass和Superclass的走向一样,找到元类(meta)之后,就直接指向根元类了(root meta class),没有中间继承类的联系,因为我们的Person直接继承的NSObject,所以看不出来,你可以创建一个类继承自Person ,然后用那个类的对象走一遍上边调试的流程验证下。对于图中最上边的root class的流程,可以直接创建一个NSObject实例对象来验证就可以了。

结语

isa的面纱暂时先揭露到此,有啥新的感悟随时补充。了解了isa,顺其自然的过渡到下一个章节—-类。