一、isa 在哪里
有如下代码,在控制台输出obj的数据结构,排在第一位的就是isa的地址。

为什么呢?因为对象继承自NSObject,NSObject在底层的实现是结构体objc_object,里面只有一个isa成员变量,那么对象的首地址指向的第一块就是isa所在。

二、isa 的类型
正常来说isa指向的就是该对象的类,那我们打印这个地址应该输出类名,但是出乎意料的是这里并没有打印出类名。

去源码里面找找看,发现isa是Class类型

Class类型其实是objc_class

可以看到objc_class继承自objc_object,那么里面就应该有一个isa。此外还有的成员变量就是superclass、cache、bits、data。

三、isa 的结构
但是不行,这样也看不出来什么呀,还是不知道isa到底是什么。有看过 iOS alloc & init 方法解析 的朋友应该有印象,在alloc方法里面会调用一个叫initIsa()的方法,那么是不是可以在这个方法中找到isa的真正结构呢?

终于找到了,原来isa_t是个联合体,看下来重点就应该在ISA_BITFIELD

喜大普奔终于找到了,注意的我们要关注arm64下的结构。前面的是参数名、后面的是所占位数,总数加起来是64位。

| 参数名 | 作用 | 大小 | 所在位置 |
|---|---|---|---|
| nonpointer | 是否对isa指针开启指针优化0:纯 isa指针只包含类对象地址1: isa中包含了类对象地址、类信息、对象的引用计数等 |
1 | 0 |
| has_assoc | 是否有关联对象 0:没有 1:存在 |
1 | 1 |
| has_cxx_dtor | 该对象是否有C++或者Objc的析构器 如果有析构函数则需要做析构逻辑 如果没有则可以更快的释放对象 |
1 | 2 |
| shiftcls | 存储类指针的值。开启指针优化的情况下,在arm64架构中有 33 位用来存储类指针 |
33 | 3~35 |
| magic | 用于调试器判断当前对象是真的对象还是没有初始化的空间 | 5 | 36~40 |
| weakly_referenced | 是否有弱引用 0:没有 1:存在 |
1 | 41 |
| deallocating | 是否正在释放内存 0:不是 1:是 |
1 | 42 |
| has_sidetable_rc | 是否需要用到外挂引用计数,当对象引用技术大于 10 则需要借用该变量存储进位 | 1 | 43 |
| extra_rc | 该对象的引用计数值,实际上是引用计数值减 1。 如果对象的引用计数为10,那么 extra_rc 为 9。如果引用计数大于 10 则需要使用 has_sidetable_rc |
19 | 44~63 |
那么我们知道了,在开启isa优化的时候对象的指针是存在isa的shiftcls里面,那么怎么获得shiftcls呢?我们强转isa的类型为isa_t后打印

四、isa 获取类
shiftcls是有了,但是这里面还不是类?这时候我们再来看shiftcls是怎么来的。

原来是cls右移3位。那么我们把它还原一下,成功~

五、怎么从对象获取类
平常获取对象的类会直接调用class方法,那么class方法内部实现是怎样的?

重点来了,getIsa()方法;

当前不是taggedPointer,直接返回ISA()

ISA()里面根据判断条件就会走到最后的一行,就是isa.bits和ISA_MASK做一下与运算

我们来看看ISA_MASK

ISA_MASK有了,但是isa.bits怎么获取呢??这里打印isa出来的数值和结构里bits的值一模一样!

那么我们尝试一下~把isa打印的值和ISA_MASK与运算~成功获取到类

六、ISA的指向
这个时候已经验证了可以通过对象的isa获取到对象的类,那么类本身也是一个对象,它的isa又是指向那里呢?

- 简单的多打印几层,我们看到打印结果有3层是
LGPerson,第一层是我们的对象,第二层是类对象。第三层虽然也是LGPerson,但是地址和第二层的不一样,说明不是同一个对象。类对象在内存中是只能存在一个的,那么第三层肯定就是元类了。 - 往上继续打印,发现元类对象
isa指向的是NSObject,NSObject的象isa指向的也是NSObject。看看2个NSObject的地址是相同的,所以NSObject的isa是指向了自身,也就是NSObject和LGPerson具有同一个根元类。
那么我们可以得到四个结论
- 对象的
isa指向其类 - 类对象的
isa指向其元类 - 元类的
isa指向根元类 - 所有元类的
isa都指向根元类
