isa の 来龙去脉

207 阅读8分钟

写在前面

首先我们在查看objc源码的时候,可以看到每个objc_object的结构里面有一个isa属性;另外我们都知道isa关系着OC对象的命脉,接下来就开始探究。

isa 结构


/// 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
};

/// 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
#   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

从结构定义可以看出isa是一个联合体,并且里面还使用了位域计算的特点,使得结构更加精简、方便理解、内存最大利用化。在不同的架构下面位域计算(告诉位置的区域)所占的内存大小不同,区别主要在于shiftcls(arm64 真机架构下占33位,x86 模拟器下占44位) 和 extra_rc(arm64 真机架构下占19位,x86 模拟器下占11位); 但是不管是什么架构下所占的整个大小都是64位 == 8字节。关于联合体和结构体的区别以及位域的知识点,请自行了解;个人理解联合体和结构体的区别在于所有成员公用一块内存,内存的大小取决于联合体中最大成员的所占用内存大小,并且同一时刻的成员是互斥的,不会存在两个成员,isa这里最大是8个字节,就是64位。位域存储的值定义为了优化内存,因为否则用很多属性取定义位域存储的类信息则需要消耗大量的内存。

具体isa中的位域存储的到底是什么呢? 从源码仲可以看到有一个标识nonpointer来区分isa的类型;nonpointer表示当前的isa除了存储isa结构之外还存储了一些类的信息,对象的引用计数等;目前大部分的isa都不是nonpointer类型的isa, 都是纯isa 指针;所以我们按照这个方向取去分析源码流程, 这里以__arm64__架构下的🌰:

属性 占用字节数 说明
nonpointer 1 表示是否对isa开启指针优化;0 - 未优化,纯isa指针结构,1 - 优化,不仅包含类对象的地址,isa还存储类信息,对象的引用计数等
has_assoc 1 关联对象的标识位,1 - 存在对象管理,0 - 不存在
has_cxx_dtor 1 当前对象是否含有 C++ 或者 Objc 的析构器,如果存在析构函数,则需要执行析构函数逻辑,如果没有,则可以在释放的时候直接更快的释放对象
shiftcls 33 重点: 存储类指针的值。开启指针优化的情况下,在 arm64 下33位存储类指针
magic 6 用于调试器判断当前对象是真的对象还是没有初始化的内存空间
weakly_referenced 1 指当前对象是否被指向或者曾经指向一个ARC环境下的变量(是否存在弱引用),如果不存在可以弱引用的对象则可以更快的释放当前对象
deallocating 1 标志当前对象是否正在释放内存
has_sidetable_rc 1 当对象的引用计数大于10时(有时候引用计数会存储在 isa 中,通常在 SideTable 散列表中); 则需要借用改变量来存储进位
extra_rc 19 表示当前对象的引用计数值,实际上引用计数值因为为1;例如,如果当前的引用计数为10,那么 extra_rc = 9。如果引用计数大于10,则需要使用上面的 has_sidetable_rc 存储

isa 初始化

在前面分析alloc流程中分析到callAlloc方法;然后调用class_createInstance方法,最后逻辑到obj->initInstanceIsa(cls, hasCxxDtor); 顾名思义,该方法就是初始化类的isa的入口。

  • objc_object::initInstanceIsa
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
  • objc_object::initIsa
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;  // 走当前分支
    } else {                                    
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;                
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;           
        newisa.shiftcls = (uintptr_t)cls >> 3; 
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

isa 原理

关联类

  • 对象在内存中可能会因为属性的内存对齐进行优化,所以对象的属性可能不是固定的,但是对象的第一个属性必然是isa指针,👇进行验证。 run-time的一个api -> object_getClass方法可以获取当前对象的class; 然后发现底层调用:
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);    // Class 等于 isa指针的bits(即isa) 与上一个mask值,类似于子网掩码的效果,盖住不需要的数据,只展示想要的数据
#endif
}

因此我们通过拿到class类对象的地址,以及isa的地址进行mask与运算,去验证isa和类关联的事实。

对象和类对象

  • 对象
    • 开辟内存创建的实例对象
    • 开发者根据类实例化出的结果,alloc所开辟的内存对象
    • 类可以创建多个
  • 类对象
    • 一个类结构的对象
    • 代码定义的类,系统创建的对象
    • 内存只有一份
// 创建两个对象,打印内存地址以及类地址
MRObject *obj1 = [MRObject alloc];
MRObject *obj2 = [MRObject alloc];

// lldb 调试打印
(lldb) x/4gx obj1 // 打印obj1的内存段 0x001d800100001269 isa
0x101a282c0: 0x001d800100001269 0x0000000000000000
0x101a282d0: 0x0000000080080000 0x00007fff916afa68
(lldb) x/4gx obj2 // 打印obj2的内存段 0x001d800100001269 isa
0x101a20500: 0x001d800100001269 0x0000000000000000
0x101a20510: 0x636f72504b575b2d 0x70756f7247737365
// 以上两个结果可以看出两个对象的地址不同,但是isa地址是相同的,而isa是关联类的 => object_getClass 中得出 isa & mask => 类对象的地址
(lldb) p/x MRObject.class   // 打印MRObject类对象的地址 0x0000000100001268
(Class) $4 = 0x0000000100001268 MRObject
// 通过上面的isa & mask(_x86_ = 0x00007ffffffffff8) 得到 0x0000000100001268 和类地址相同
(lldb) p/x 0x001d800100001269 & 0x00007ffffffffff8
(long) $5 = 0x0000000100001268
// 类地址打印的结果即为MRObject类,所以类对象是只有一份的!
(lldb) po 0x0000000100001268
MRObject

isa走向、类继承

类、元类、根源类

  • 类 class
    • 代码构造的类,系统创建的对象
  • 元类 meta_class
    • 类的isa指向的虚拟类,系统创建的;在编译阶段,发现存在类对象,为了方便存储类方法信息等,创建的该类的元类对象
  • 根元类 root_meta_class
    • NSObject 的元类,由于 NSObject 是所有类的根类

类 -> 元类 -> 根元类

isa 走向图

苹果爸爸官方权威的经典isa走位图👇👇

isa走位图

调试验证:

MRObject 继承自 NSObject


void testMRObject() {
    // MRObject实例对象
    NSObject *object1 = [MRObject alloc];
    // MRObject类
    Class class = object_getClass(object1);
    // MRObject元类
    Class metaClass = object_getClass(class);
    // MRObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // MRObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"MRObject isa -----------------\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}

void testNSObject() {
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"NSObject isa -----------------\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}

NSLog

NSObject isa -----------------
0x10183e7d0 实例对象
0x100b35140 类
0x100b350f0 元类
0x100b350f0 根元类 [NSObject的元类也是根元类]
0x100b350f0 根根元类 == 根元类的isa [说明根元类isa指向自己]

MRObject isa -----------------
0x10183e3b0 实例对象
0x100001298 类
0x100001270 元类
0x100b350f0 根元类 == NSObject元类 [说明根类是NSObject]
0x100b350f0 根根元类
  • 继承

    • 子类 superClass -> 父类 -> 根类(NSObject)
    • 子元类 superClass -> 父元类 -> 根元类(NSObject的元类)
    • !!! 根元类 -> 根类 (NSObject) 形成闭环
  • isa 指向

    • 实例对象(xxxInstance) isa -> 类对象(xxxClass) isa
    • 类对象(xxxClass) isa -> 元类对象(xxxmetaClass) isa
    • 元类对象(xxxmetaClass) isa -> 根元类对象(NSObject_meta_Class) isa
    • 根元类对象(NSObject_meta_Class) isa -> 根元类对象(形成闭环)

总结

万物皆对象,isa 指向贯穿 OC 整个对象和类的线索,包括类、KVO、方法的查找流程等;都是依靠isa这条基线做的处理,所以了解本质是尤其重要的,持续学习~