isa 存储 cls 信息原理

831 阅读3分钟

前置文章 OC 对象 alloc 流程分析

本文研究 alloc 流程中重要的三个函数之一,initInstanceIsa isa 的初始化

isa 初始化源码:


// 1.`_class_createInstanceFromZone` 函数调用
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        // 2. nonpointer 参数值为 true
        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;
        // 3.存储 cls 信息
        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 介绍

// 宏定义
#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)

// 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 中存储了一个 bits 属性,在 x86_64 平台,长度为 8 字节(占用 64 位)
这 64 位进行了固定分配,来存储指定的信息。

如下面的二进制数据,就是一个 isa.bits 内容,对应上图是从右到左

0b 00000000 0 0 0 111011 00000000000000000000000000000000000000000000 0 0 1

cls 存储到 isa

下面我们来看一下 cls 是怎么过存在 bits 中的

赋值前

我们把断点打在图中位置。输出如下信息

(lldb) p newisa
(isa_t) $3 = {
  cls = 0x001d800000000001
  bits = 8303511812964353
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}
(lldb) p/t 8303511812964353 // 输出 bits 二进制
(long) $4 = 0b0000000000011101100000000000000000000000000000000000000000000001
(lldb) p/t (uintptr_t)cls   // 输出 cls 指针二进制
(uintptr_t) $5 = 0b0000000000000000000000000000000100000000010001110100000000111000
(lldb) p/t (uintptr_t)cls >> 3  // 右移三位
(uintptr_t) $6 = 0b0000000000000000000000000000000000100000000010001110100000000111

可以看出,在未设置 shiftcls 时,bits 从右到左 [3, 46] 位都是0.

0b00000000000111011 00000000000000000000000000000000000000000000 001

为什么要右移三位?

在 Objective-C 中,类的指针是按照字节(8 bits)对齐的,也就是说类指针地址转化成十进制后,都是8的倍数,也就是说,类指针地址转化成二进制后,后三位都是0。既然是没有意义的0,那么在存储时就可以省略,用节省下来的空间存储一些其他信息。

赋值后

然后让断点往下走一步,如下图

输出信息如下:

(lldb) p newisa
(isa_t) $7 = {
  cls = OS_dispatch_data
  bits = 8303516112601145
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 537454599
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}
(lldb) p/t 8303516112601145 // 将 bits 转为二进制
(long) $8 = 0b0000000000011101100000000000000100000000010001110100000000111001

可以看到,现在的 bits 的 [3, 46] 位正好是之前 cls 指针右移三位的内容 由于1和0宽度不同,因此没有对齐

赋值前: 0b00000000000111011 00000000000000000000000000000000000000000000 001
赋值后: 0b00000000000111011 00000000000000100000000010001110100000000111 001

转换过程如下图

至此,cls 信息已经存至 isa,其他 isa 信息的存储原理类似。


参考文章
Runtime中的 isa 结构体