通过之前的介绍我们知道了对象是如何创建的,但是呢,这里还存在一个小细节,在_class_createInstanceFromZone方法中执行完obj = (id)calloc(1, size);这句之后obj还并不是我们要的对象,当执行完obj->initInstanceIsa(cls, hasCxxDtor);这句之后obj才是我们创建的MYPerson对象,当时说这是因为这里初始化了一个isa,并和类进行了关联,我们也知道通过对象的isa可以找到所属的类,今天我们就探究一下isa

一:isa是什么
在开头我们先介绍一下,isa有两种,一种是单纯指针(Class类型),另一种是nonpointer还包含一些其他信息,以优化内存。我们都知道一个isa占8字节64位,所以这每一位都包含一些类的相关信息,便于进行内存优化。
1. isa的定义
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
2. Class的定义
其实 Class 是一个 objc_class 的指针
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
.....此处省略
}
3. bits定义
typedef unsigned long uintptr_t;
从上面的定义中可以发现,isa其实是一个isa_t联合体,拥有cls和bits两个成员。联合体又是什么?联合体会与其内部成员共用内存,内存的大小取决于成员中占用内存最大的那个,无论是结构体指针还是unsigned long,都是8字节,所以isa_t就是8字节。
二:关于isa的内存优化
我们从isa_t联合体中的位域ISA_BITFIELD入手
1. 位域
以下是百度百科对位域的定义
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息
2. ISA_BITFIELD定义
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL // ULL代表16进制的后缀 掩码
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
从代码中我们可以看到,有arm64和x86_64两种架构,但是位域的大小都是 8 字节,64 位。
3. 每一位代表什么
- nonpointer: 表示是否对 isa 指针开启指针优化
- 0: 纯 isa 指针
- 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等
- has_assoc: 关联对象标志位,0 没有,1 存在
- has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
- shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
- magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间
- weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
- deallocating: 标志对象是否正在释放内存
- **has_sidetable_rc:**当对象引用技术大于10时,则需要借用该变量存储进位
- extra_rc: 当表示该对象的引用计数值,实际上是引用计数值减 1
从上面看出,位域中存储着很多的信息,已经将这8字节64位用到极致了。shiftcls需要注意下,它存储着类指针的值,其实也是通过它来找到所属的类
三:isa指向分析
类继承于NSObject,那么必然会有isa,而且isa必然在首位

我们通常用Class stuClass = [stu1 class]这种方式获取到对象所属的类,那么它是怎么获取的呢?我们通过源码来看一下
首先看class方法的内部实现,我们发现调用了object_getClass(self)方法
- (Class)class {
return object_getClass(self);
}
object_getClass(self)的内部实现
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
getIsa内部实现
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
这里关键的点就在于ISA(),看下其内部实现
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
以上的源码是调用的过程,最终代码会走到return (Class)(isa.bits & ISA_MASK),这里返回的是进行按位与运算的数值强转为 Class 的结果。其中ISA_MASK是掩码,在上面位域中已经有定义 --> 0x0000000ffffffff8ULL
我们来验证我们的猜想
先打印对象stu1的内存结构,那么0x001d8001000014a9代表的就是isa
(lldb) x/4xg stu1
0x1018131d0: 0x001d8001000014a9 0x0000000000000000
0x1018131e0: 0x72726f43534e5b2d 0x65546e6f69746365
(lldb)
这时候我们打印0x001d8001000014a9是什么都看不出来的
(lldb) po 0x001d8001000014a9
8303516107936937
我们尝试下这么操作(isa.bits & ISA_MASK),加上掩码。
(lldb) p/x 0x001d8001000014a9 & 0x0000000ffffffff8
(long) $7 = 0x00000001000014a8
(lldb) po 0x00000001000014a8
WYStudent
此时已经验证了我们的猜想,通过isa我们获取到了类的信息。
通过上面同样的方式来验证,类是isa指向元类,元类的isa指向根元类,根元类的isa指向自己.

