objc中的isa

本文基于objc4-750源码.

如果我们对Objective-C有所了解, 那么应该知道每个对象都是C语言结构体, 每个结构体中都会有一个isa指针, 但是最新版本中isa已经不再如此:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

// objc-object 的声明
struct objc_object {// 声明 objc_object 对象, 内部有一个 objc_class
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA; // 继承自 objc_object 类型
    Class superclass;// 父类
    cache_t cache;             // formerly cache pointer and vtable -- 方法缓存
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    ...
}

// objc_object 的实现
struct objc_object {
private:
    isa_t isa;// 1.可能是 Class, 2.也有可能是 bits

public:

    // 因为我们使用结构体取代了原有的 isa 指针,所以要提供一个方法 ISA() 来返回老版本的类指针。 其中 ISA_MASK 是宏定义,这里通过掩码的方式获取类指针, 编译器对直接访问 isa 的操作会有警告,因为直接访问 isa 已经不会返回类指针了,这种行为已经被弃用了,取而代之的是使用 ISA() 方法来获取类指针。
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    ...
}

我们可以看到新版本中的isa不再是一个单纯的指针, 而是一个isa_t的结构体, 并且这个isa结构体是private的. 与此同时, 不仅仅objc_objectisa结构体, 每个类objc_class也有isa结构体, 因为objc_class继承自objc_object.

这里我们得到了一个结论: Objctive-C中的类也是一个对象.

isa与元类

这里要引入一个概念元类, 我们用meta-class来表示. 下图中标志的非常清楚, 每个对象isa会指向, 每个isa会指向元类. objc_class就即可以表现成class 类, 也可以表现成meta-calss 元类, 从而使得实例方法的调用和类方法的调用机制达到统一:

  1. 调用实例对象方法时, 通过实例对象(objc_object)的isa找到类(objc_class), 从类中获取方法表(method_lists)从而获取对应的实例方法.
  2. 调用类方法时, 通过类(objc_class)的isa找到元类(meta_class), 从元类中获取类方法表(method_lists), 从而获取对应的类方法
objc中的isa_第1张图片
class-diagram.jpg

isa的定义

isa_t的定义如下:

union isa_t {
    isa_t() {} // union 的构造函数
    isa_t(uintptr_t value) : bits(value) {}

    Class cls;
    uintptr_t bits;//typedef unsigned long uintptr_t;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

isa.h中有如下代码:

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

...

#endif

可以清楚的看到, isa_t和系统的处理器架构有关. 本节用__x86_64__表示:

objc中的isa_第2张图片
isa_bit.jpg
struct {
    uintptr_t nonpointer        : 1; //表示 isa_t 的类型, 是否是指针, 还是
    uintptr_t has_assoc         : 1; //表示对象是否有关联对象
    uintptr_t has_cxx_dtor      : 1; //表示对象是否有c++析构函数
    uintptr_t shiftcls          : 44;//存储Class,Meta-Class对象的内存地址
    uintptr_t magic             : 6;//被调试器用来从没初始化的东西中区分真是的对象
    uintptr_t weakly_referenced : 1;//对象是否有若引用
    uintptr_t deallocating      : 1;//对象是否在释放
    uintptr_t has_sidetable_rc  : 1;//对象是否引用计数太大,需要使用sidetable计数
    uintptr_t extra_rc          : 8;//对象的引用计数是extra_rc 值+1(举例,如果 extra_rc 是5,那么对象的真是引用计数是6)
};

具体解释:

  1. nonpointer
    • 0 代表普通的指针, 当前isa内部存储着Class, Meta-Class对象的内存地址.
    • 1 代表优化过, 使用位域存储更多的信息, 当前结构体才有用!!!!
  2. has_assoc
    • 是否有设置过关联对象,如果没有,释放时会更快
  3. has_cxx_dtor:
    • 表示该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象
  4. shiftcls
    • 这里面比较重要的就是shiftcls这个成员变量, 这个成员变量里面存放的就是Class,Meta-Class的地址值, 要取得这个值, 需要bits & ISA_MASK
  5. magic
    • 用于在调试时分辨对象是否未完成初始化
  6. weakly_referenced
    • 是否有被弱引用指向过,如果没有,释放时会更快
  7. deallocating
    • 对象是否正在释放
  8. has_sidetable_rc
    • 当对象引用计数大于一定时,则has_sidetable_rc 的值为 1, 那么引用计数会存储在一个叫 SideTable 的类的属性中
  9. extra_rc
    • 对象的引用计数是extra_rc 值+1(举例,如果 extra_rc 是5,那么对象的真是引用计数是6)

关于对象的引用计数:

在 64 位环境下, pointer优化的 isa 指针并不是就一定会存储引用计数, 毕竟用 8bit 保存引用计数不一定够. 需要注意的是这 8 位保存的是引用计数的值减一. has_sidetable_rc 的值如果为 1, 那么多出来引用计数会存储在一个叫 SideTable 的类的属性中, 当一个对象的引用计数很大时(extra_rc 超出所能表示的范围), 需要它辅助记录对象的引用计数.

此时实际的计数值:retainCount = 1 + extra_rc + sideTable.refcnts[obj] 中的值。

散列表存储引用计数具体使用的DenseMap实现, objc_object底层会维护一个 SideTable(DenseMap)

isa_t的初始化

void initIsa(Class cls /*nonpointer=false*/); // 这个是 Isa 的init 方法

/*
 initIsa() should be used to init the isa of new objects only.!!!!

 这个方法会在[[NSObject alloc] init]; 时候调用

 从前面可以知道, 普通对象的isa指向的是对象的类(Class)
 */
inline void 
objc_object::initIsa(Class cls) {
    // 方法实现
    initIsa(cls, false, false);
}

/*
 从方法的命名可以看出. 这里初始化的是 Class 的isa!!!

 从上面知道, Class的isa应该指向metaClass
 */
inline void
objc_object::initClassIsa(Class cls) {
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
    //1. 确保当前objc_object 不是 tagged_pointer
    assert(!isTaggedPointer());

    //2. 在initIsa时, nonpointer = false;  会走第一个{}. 将 cls赋值给 isa.cls
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        //3. 如果是 nonpointer = true. 那么需要新建一个 newisa, 然后设置bits 等内容, 然后将objc_object.isa 设置
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());
        //创建一个新的 isa_t, 用isa_t保存更多的信息
        isa_t newisa(0);

        // 会进入这里!!! #define ISA_MAGIC_VALUE 0x001d800000000001ULL

        //在通过了解内存结构以后, 实际上只是设置了 nonpointer 以及 magic 这两部分的值。 其中 nonpointer 表示 isa_t 的类型, 0表示 raw isa. 也就是没有结构体部分, isa会直接返回一个cls指针. 也就是iOS在64位系统以前的isa类型. 新版本iOS都是64位系统, 目前 nonpointer = 1, 关于类的指针都是保存在 shiftcls
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.bits = ISA_MAGIC_VALUE;
        // 在设置 nonpointer 和 magic 值之后,会设置 isa 的 has_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。
        newisa.has_cxx_dtor = hasCxxDtor;
        //在为 nonpointer,  magic 和 has_cxx_dtor 设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
        //绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。同时OC中类指针的地址后三位也是0, 所以所有类指针16进制的最后一位都是8或者0. 因此在这里右移3位是没有问题的.
        newisa.shiftcls = (uintptr_t)cls >> 3;

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

3个要点:

  1. 初始化时候, 需要外部传入参数nonpointer, 标志isa是普通指针还是isa_t结构体
  2. newisa.bits = ISA_MAGIC_VALUE; 会一次性设置magicnonpointer两个值, nonpointer设置成1, 表示当前isa_t是 nonpointer!!!
  3. newisa.shiftcls = (uintptr_t)cls >> 3;class的地址存储在isa.shiftcls中.

其中难点在于shiftcls存储class或者meta_class的地址, 我们可以打印出的 [NSObject class] 指针, 其最后3bit都是0. 因此源码中右移3位完全正确.

ISA()新方法

因为我们使用结构体取代了原有的 isa 指针,所以要提供一个方法 ISA() 来返回类指针。

其中 ISA_MASK 是宏定义,这里通过掩码的方式获取类指针:

#define ISA_MASK 0x00007ffffffffff8ULL
inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

参考

https://github.com/draveness/analyze/blob/master/contents/objc/%E4%BB%8E%20NSObject%20%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%86%E8%A7%A3%20isa.md#arm64
https://www.jianshu.com/p/7240988c6be6
https://juejin.im/post/5b18f5af5188257d7a49b331
https://www.jianshu.com/p/74db5638f34f

你可能感兴趣的:(objc中的isa)