Objective-C runtime源码小记-再谈isa

1,915 阅读5分钟

本文基于可编译的objc4-750版本源码进行讨论,文章中旦有错误之处,希望大家不吝指正。 如果想要获取最新源码:objc最新源码在这里

前言

在探究OC runtime 对象和类的内部实现中(位于objc-private.h),isa是非常关键的成员,runtime中对象就是通过isa来获取所属的Class。其实,早期的isa就是指向Class的指针,但是现在,它已经不是简单的指向对象的指针了。

getIsa()

从runtime源码中,可以看到isa是一个私有成员,你不能直接通过访问isa成员来获取Class信息了,必须通过runtime中提供的相应的接口getIsa():

struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    // ...
}

那么如何通过getIsa()接口获取Class呢?来到getIsa()方法实现(objc-object.h中):

inline Class
objc_object::getIsa()
{
    // 如果this是 tagged pointer,对象信息存在了this指针自身的内存空间
    // 否则通过ISA()方法从this->isa 成员中解析
    if (!isTaggedPointer()) return ISA();

    // 从tagged pointer中解析出Class type index,然后从类型索引表中查找对应的Class
    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) { // 扩展的tagged pointer
        uintptr_t slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else { // 普通的tagged pointer
        uintptr_t slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}

getIsa()方法中,会先判断this指针是否是tagged pointer,如果是的话,Class信息直接存放在指针本身的内存空间中,所以从指针中解析获取Class。关于Tagged pointer,可以看之前的这篇《Objective-C Tagged Pointer

this为非 tagged pointer的话,才通过ISA()方法(objc-object.h中),从isa私有成员中获得Class,然而ISA()方法中,也不是直接返回this->isa指向的对象的:

inline Class
objc_object::ISA()
{
    assert(!isTaggedPointer());

#if SUPPORT_INDEXED_ISA // 目前主要是watch设备支持Indexed isa
    if (isa.nonpointer) { 
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;// 普通指针
#else
/*
     以下这行等同于:
     if (isa.nonpointer) { // nonpointer格式,通过按位与ISA_MASK来获取结果
         return (Class)(isa.bits & ISA_MASK);
     } else { // 普通指针
         return (Class)isa.bits;
     }
     Packed non-pointer 和普通的pointer存放地址的位域相同,因此两种都可通过按位与ISA_MASK来获取结果
     */
    return (Class)(isa.bits & ISA_MASK);
#endif
}

这边涉及到了non-pointer,什么是non-pointer?

Non-Pointer

一个指针(地址)需要用64位也就是16-byte的内存空间来存储,但实际上往往用不到全部,只用到了中间的部分,高位16-bit用不到,由于内存按字节对齐的要求,所以低位值也总是为0。因此,将指针中除了存放地址之外的位利用起来存放额外的信息,这种非纯粹的指针就是non-pointer(感觉跟tagged pointer类似,都是对指针空间的最大化利用)。 09EDEC69-0FE7-4FF8-A827-11258363918E.png

回到runtime源码,isa的类型是isa_t, 继续查看isa_t的定义(objc-private.h中):

// isa_t 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_t是一个共用体,它本质上是占用了一个指针的内存空间,程序中它既可以是一个Class,也可以是一个叫bits的指针,或者是用ISA_BITFIELD宏定义的位域结构体。

查看(isa.h)中关于 ISA_BITFIELD宏的定义,可以了解non-pointer isa 的具体是如何存储的:

#if SUPPORT_PACKED_ISA
    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extraBytes : 1;  // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;/*标记是否为普通指针,0表示普通指针,1表示non-pointer指针,每一位存储各种数据*/ \
      uintptr_t has_assoc         : 1;/*表示该对象是否包含 associated object,如果没有,则析构时会更快*/ \
      uintptr_t has_cxx_dtor      : 1;/*表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快*/ \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000 类的指针*/ \
      uintptr_t magic             : 6;/*固定值为 0xd2,用于在调试时分辨对象是否未完成初始化*/ \
      uintptr_t weakly_referenced : 1;/*表示该对象是否有过 weak 对象,如果没有,则析构时更快*/ \
      uintptr_t deallocating      : 1;/*表示该对象是否正在析构*/ \
      uintptr_t has_sidetable_rc  : 1;/*表示该对象的引用计数值是否过大无法存储在 isa 指针*/ \
      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
// SUPPORT_PACKED_ISA
#endif

#if SUPPORT_INDEXED_ISA
# if  __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
    // armv7k or arm64_32
#   define ISA_INDEX_IS_NPI_BIT  0
#   define ISA_INDEX_IS_NPI_MASK 0x00000001
#   define ISA_INDEX_MASK        0x0001FFFC
#   define ISA_INDEX_SHIFT       2
#   define ISA_INDEX_BITS        15
#   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
#   define ISA_INDEX_MAGIC_MASK  0x001E0001
#   define ISA_INDEX_MAGIC_VALUE 0x001C0001
#   define ISA_BITFIELD                         \
      uintptr_t nonpointer        : 1;          \
      uintptr_t has_assoc         : 1;          \
      uintptr_t indexcls          : 15;         \
      uintptr_t magic             : 4;          \
      uintptr_t has_cxx_dtor      : 1;          \
      uintptr_t weakly_referenced : 1;          \
      uintptr_t deallocating      : 1;          \
      uintptr_t has_sidetable_rc  : 1;          \
      uintptr_t extra_rc          : 7
#   define RC_ONE   (1ULL<<25)
#   define RC_HALF  (1ULL<<6)
# else
#   error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif

具体来说,non-pointer isa根据CPU架构不同会有几种不同的存储方式,但主要分为两大类:

  • Packed Isa - 存放类指针的位域(shiftcls)和普通的isa指针一样,数据存放在其他位域,因此通过和ISA_MASK做按位与运算就能取得实际的指针地址。

  • Indexed Isa - 这个是运行在watch设备上的,因为watch设备ABI的指针只有32位,没有足够的空间来像Packed isa一样存放除了地址之外的其他数据,因此它不是直接存放指针地址,而是在位域(indexcls)中存放了一个class索引表(一个array)的index值,Class第一次加载到内存中的时候添加到class索引表, 如果索引表元素超过容量,则不再使用Indexed isa格式,回退到普通的isa。

总结:

  1. 获取对象的Class,需要通过getIsa()接口
  2. 首先需要判断它是不是普通pointer还是Tagged pointer,因为Tagged pointer的Class信息存放在指针本身,它并不实际指向一个objc_object变量(对象)。
  3. 接着判断isa是不是non-pointer,普通指针指向的就是Class,non-pointer的话通过位运算的取出Class。

22.png