上篇 objc对象的底层探索(上)说到,对象的本质是一个objc_object的结构体,其结构体内存储的是 isa指针 + 成员变量的值。本篇来着重说明一下isa指针。
结构体与联合体
在isa指针前,需要补充两个概念,结构体与联合体。
构造数据类型的方式有以下两种:
结构体(struct)
联合体(union,也称为共用体)
结构体
结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。结构体的各个成员会占用不同的内存,互相之间没有影响。
-
优点:存储
容量较大,包容性强,且成员之间不会相互影响(占用不同内存)。 -
缺点:结构体内存
>=所有成员占用的内存总和。所有属性都分配内存,比较浪费内存。假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费。
联合体
联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,一次只能使用一个成员,如果对新的成员赋值,就会将原来成员的值覆盖掉,联合体中可以定义多个成员,联合体的大小由最大的成员大小和基础数据类型的整数倍决定,共用体占用的内存等于基础数据类型的整数倍且能覆盖到最大的成员占用的内存。
-
优点:
所有成员共用一段内存,修改一个成员会影响其余所有成员。 使内存的使用更为精细灵活,同时也节省了内存空间,存储效率更高。 -
缺点:每个变量是
互斥的,且包容性差。
联合体代码定义如下:
可以看到,分别打印三次赋值的输出,只有当前成员变量赋值生效。
再看一下大小的计算,按基础数据整数倍(8的倍数)且容纳最大成员变量占用内存(11),所以该结构体大小为16。
isa的结构
上篇(见顶部)中知道,_class_createInstanceFromZone中会调用initIsa方法
方法中可以看到一个isa_t,原来isa_t是一个联合体,看下来重点就应该在ISA_BITFIELD
使用__x86_64__举例
| 参数名 | 作用 | 大小 | 所在位置 |
|---|---|---|---|
| nonpointer | 是否对isa指针开启指针优化0:纯isa指针只包含类对象地址1:isa中包含了类对象地址、类信息、对象的引用计数等 | 1 | 0 |
| has_assoc | 是否有关联对象0:没有1:存在 | 1 | 1 |
| has_cxx_dtor | 该对象是否有C++或者Objc的析构器 如果有析构函数则需要做析构逻辑如果没有则可以更快的释放对象 | 1 | 2 |
| shiftcls | 存储类指针的值。开启指针优化的情况下,在arm64架构中有 44 位用来存储类指针 | 44 | 3~46 |
| magic | 用于调试器判断当前对象是真的对象还是没有初始化的空间 | 6 | 47~52 |
| weakly_referenced | 是否有弱引用0:没有1:存在 | 1 | 53 |
| unused | 是否正在释放内存0:不是1:是 | 1 | 54 |
| has_sidetable_rc | 是否需要用到外挂引用计数,当对象引用技术大于 10 则需要借用该变量存储进位 | 1 | 55 |
| extra_rc | 该对象的引用计数值,实际上是引用计数值减 1。 如果对象的引用计数为10,那么 extra_rc 为 9。如果引用计数大于 10 则需要使用 has_sidetable_rc | 8 | 56~63 |
那么我们知道了,在开启isa优化的时候对象的指针是存在isa的shiftcls里面,那么怎么获得shiftcls呢?我们强转isa的类型为isa_t后打印
isa类获取
根据isa指针内存分配位置可知,只需要将isa中的地址shiftcls右移3位即可获取到类,见上图(使用__x86_64__举例)
怎么从对象获取类
平常获取对象的类会直接调用class方法,那么class方法内部实现是怎样的?
通过方法一层一层找下来,会找到一个掩码ISA_MASK
那我们是不是可以通过这个掩码来获取一下类呢,我们来实验一下
isa指向探索
这个时候已经验证了可以通过对象的isa获取到对象的类,那么类本身也是一个对象,它的isa又是指向那里呢?
-
简单的多打印几层,我们看到打印结果有3层是
LGPerson,第一层是我们的对象,第二层是类对象。第三层虽然也是LGPerson,但是地址和第二层的不一样,说明不是同一个对象。类对象在内存中是只能存在一个的,那么第三层肯定就是元类了。 -
往上继续打印,发现元类对象
isa指向的是NSObject,NSObject的象isa指向的也是NSObject。看看2个NSObject的地址是相同的,所以NSObject的isa是指向了自身,也就是NSObject和LGPerson具有同一个根元类。
那么我们可以得到四个结论
- 对象的
isa指向其类 - 类对象的
isa指向其元类 - 元类的
isa指向根元类 - 所有元类的
isa都指向根元类 - 所有根元类的
isa都指向它自己