美文网首页
iOS - isa 和 类的关系探索

iOS - isa 和 类的关系探索

作者: malgee | 来源:发表于2020-09-14 15:43 被阅读0次

    我们使用的所有对象的都是继承至NSObject, 你是否会探索下NSObject到底是啥?
    点进去可以看到出了方法之外只有一个变量 Class isa

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    

    点击 Class可以可以发现是结构体objc_class

    #if !OBJC_TYPES_DEFINED
    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    #endif
    

    继续点击objc_class可以发现定义的方式, 发现继承至 objc_object, 通过上面定义的 objc_object结构体可以看到只有一个 isa变量

    struct objc_class : objc_object {
        // Class ISA;   
        Class superclass;  
        cache_t cache;            // formerly cache pointer and vtable
        class_data_bits_t bits;   
        ....
    }
    

    通过objc4源码中找到定义结构体 objc_object, 可以发下也只有一个私有的变量 isa, 那么isa到底是什么呢?或者说 干了什么

    struct objc_object {
    private:
        isa_t isa;
    
    public:
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA();
    
        // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
        Class rawISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
    
        . . .
    }
    

    接续查找 isa_t发现定义 isa类型的结构是一个联合体(union 也称为共用体)

    这里补充下知识点 结构体 和 联合体

    构造数据类型的方式分为两种方式:

    • 结构体 (struct)
    • 联合体 (union, 也称为共用体)
    1. 结构体

    结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。

    【缺点】:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费

    【优点】:存储容量较大,包容性强,且成员之间不会相互影响

    2. 联合体

    联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉

    【缺点】:包容性弱,

    【优点】:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

    两者的区别

    • 内存占用情况

      • 结构体的各个成员会占用不同的内存,互相之间没有影响
      • 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员
    • 内存分配大小

      • 结构体内存 >= 所有成员占用的内存总和(成员之间可能会有缝隙)
      • 共用体占用的内存等于最大的成员占用的内存

    isa 的类型 isa_t

    通过联合体 (union)定义的 isa_t类型, 其中 clsbits是互斥的,可以通过前面分析alloc & init探索 中查看具体赋值。 其中 cls存储具体的类, bits存放类的具体信息。

    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类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说, isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。

    bits存放的类的更多信息可以通过 宏定义 ISA_BITFIELD中查看

    ---------------  arm64 架构 ----------------
    
    # 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)
    
    ---------------  x86_64 架构 ----------------
    # 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)
    
    
    • nonpointer : 表示优化的指针, 看这个Tagged Pointer 优化轻量级的内存,代表优化的指针, nonpointer
      · 0: 代表普通的指针(纯isa指针),存储着Class、Meta-Class对象的内存地址
      · 1: 代表优化过,包含了类的一些信息、对象的引用计数等

    • has_assoc : 表示 对象含有或者曾经含有关联引用(关联对象),没有关联引用的可以快速释放内存。

    • has_cxx_dtor : 表示当前对象是否有C++或者Objc的析构器(类似于dealloc, dispose), 如果没有析构器就会快速释放内存

    • shiftcls : 存储着Class、Meta-Class对象的内存地址信息,即类的信息,
      · 其中在arm64架构下占用 33 位,在x86_64中占用44位

    • magic : 用于在调试时分辨对象是否初始化,占用 6 位

    • weakly_referenced : 是否有被弱引用指向过,如果没有,释放时会更快。
      · 指对象是否被指向 或者 曾经指向一个ARC的弱变量

    • deallocating : 对象是否正在释放

    • has_sidetable_rc : 如果是 1 对象的引用计数太多了,存不下了,
      · 如果为1 ,引用计数器过大无法存储在isa
      · 那么引用计数会存储在一个叫SideTable (散列表)的类的属性中

    • extra_rc : 额外引用计数,如果has_sidetable_rc为 1,会存储在这个里面
      · 里面存储的值是引用计数器减1
      · 如果对象的引用计数为10,那么extra_rc为9

    image.png
    image.png
    注:上面分析的是在arm64架构下的shiftcls占33位,在x86_64下构下shiftcls占44位

    isa 与 类 的关联

    clsisa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来,有以下几种验证方式:

    【方式一】通过initIsa函数中的newisa.shiftcls = (uintptr_t)cls >> 3验证

    【方式二】通过isa指针地址与ISA_MSAK 的值 & 来验证

    【方式三】通过runtime的方法object_getClass验证

    【方式四】通过位运算验证

    方式一: 通过initIsa函数初始化赋值
    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());
    
            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;
        }
    }
    

    例如自定义类MGPerson初始化是也会调用 initIsa ()函数,接下来分析 isa的关系

    image.png

    在248行给isashiftcls赋值MGPerson类的,这里就是将 cls类 和 isa指针通过shiftcls位域关联,存储着类的信息

    方式二: 通过 isa & ISA_MSAK
    • 执行x/4gx person 得到isa指针的地址0x001d80010000145d

    • isa指针地址 & ISA_MASK

      • arm64中,ISA_MASK 宏定义的值为0x0000000ffffffff8ULL

      • x86_64中,ISA_MASK 宏定义的值为0x00007ffffffffff8ULL

    image.png
    方式三:通过 object_getClass 获取类和isa 关联

    通过查看object_getClass的源码实现,同样可以验证isa与类关联的原理

    Class object_getClass(id obj)
    {
       if (obj) return obj->getIsa();
       else return Nil;
    }
    
    inline Class 
    objc_object::getIsa() 
    {
       if (fastpath(!isTaggedPointer())) return ISA();
    
       extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
       uintptr_t slot, ptr = (uintptr_t)this;
       Class cls;
    
       slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
       cls = objc_tag_classes[slot];
       if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
           slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
           cls = objc_tag_ext_classes[slot];
       }
       return cls;
    }
    
    
    image.png

    执行到174行会发现和方式二的原理一样,

    方式四:通过位运算
    • 其中x/4gx person 得到isa 指针的地址 0x001d80010000145d

    • 上面分析isa的结构可知右移3位是 nonpointerhas_assoc :has_cxx_dtor :,并没有存储类的信息, 所以抹去最后三位, 即右移3位

    • 因为中间44位存储着类的信息(前面分析的x86_64 架构下isa)所以高 17位没有需要抹除,加上第一步右移3位, 一共需要左移20位

    • 0x0002000028b00000 右移17位复原shiftcls的位置, 等到最终的类的地址

    • 执行打印 0x0000000100001458地址 等到 MGPerson

    image.png

    相关文章

      网友评论

          本文标题:iOS - isa 和 类的关系探索

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