美文网首页
objc中的isa

objc中的isa

作者: brownfeng | 来源:发表于2019-02-26 21:44 被阅读14次

    本文基于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), 从而获取对应的类方法
    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__表示:

    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

          本文链接:https://www.haomeiwen.com/subject/qsttuqtx.html