美文网首页
[iOS] isa结构

[iOS] isa结构

作者: 沉江小鱼 | 来源:发表于2021-04-08 18:19 被阅读0次

    1. isa介绍

    arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。

    arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息,不只包含类或者元类信息,还包含是否有弱引用是否有关联对象等信息,这样在对象释放的时候,才能将弱引用和关联对象一起释放,所以我们拿到isa之后还需要进行一次位运算(&ISA_MASK)才能计算出Class或者Meta-Class的真实地址,如下图所示:

    image.png

    在了解为什么要进行一次位运算(&ISA_MASK)才能计算出Class或者Meta-Class的真实地址之前,我们需要先了解一下isa的源码。

    2. isa源码

    objcisa源码:

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    };
    

    runtime723版本以前,直接把结构体放在isa里面了。750版本之后,抽成宏了,如下所示:

    # 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)
    
    # else
    #   error unknown architecture for packed isa
    # endif
    

    下面的代码对isa_t中的结构体进行了位域声明,位域也是对结构体内存布局进行了一个声明,通过下面的结构体成员变量可以直接操作某个地址。位域总共占8字节,所有的位域加在一起正好是64位:

     define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;   //指针是否优化过                                   \
          uintptr_t has_assoc         : 1;   //是否有设置过关联对象,如果没有,释放时会更快                                   \
          uintptr_t has_cxx_dtor      : 1;   //是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快                                     \
          uintptr_t shiftcls          : 33; //存储着Class、Meta-Class对象的内存地址信息 \
          uintptr_t magic             : 6;  //用于在调试时分辨对象是否未完成初始化                                     \
          uintptr_t weakly_referenced : 1;  //是否有被弱引用指向过,如果没有,释放时会更快                                     \
          uintptr_t deallocating      : 1;  //对象是否正在释放                                     \
          uintptr_t has_sidetable_rc  : 1;  //引用计数器是否过大无法存储在isa中                                     \
          uintptr_t extra_rc          : 19 //里面存储的值是引用计数器减1
    #       define RC_ONE   (1ULL<<45)
    #       define RC_HALF  (1ULL<<18)
    
    
    

    isa中不同的位域代表不同的含义。

    2.1 nonpointer
    • 值为0,代表这是普通的指针,只存储着Class、Meta-Class对象的内存地址;
    • 值为1,代表isa被优化过,使用位域存储更多的信息。

    这个我们在阅读源码时,经常会遇到这个判断,我们以初始化isa的方法为例,去探究一下,如下:

    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()); 
        
        // 如果nonpointer为false,直接使用cls地址初始化isa
        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
            // #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
            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
    ...
    }
    

    从上面方法中可以看到,传递的第二个参数nonpointertrue,在下面initIsa方法中设置isa的初始值:newisa.bits = ISA_MAGIC_VALUEISA_MAGIC_VALUE值为0x001d800000000001ULL,转换为二进制之后末位为1,说明newisa. nonpointer为1,标明优化过,使用位域存储更多的信息。

    2.2 has_assoc
    • 是否有设置过关联对象,如果没有,释放时会更快(在对象dealloc时,会通过这个标志判断是否有关联对象,如果有,则会释放关联的对象

    这一点可以在研究Category原理时看到,给对象设置了关联对象之后,需要将对象的isahas_assoc设置为true,相关的代码如下:

    inline void
    objc_object::setHasAssociatedObjects()
    {
        if (isTaggedPointer()) return;
    
     retry:
        isa_t oldisa = LoadExclusive(&isa.bits);
        isa_t newisa = oldisa;
        if (!newisa.nonpointer  ||  newisa.has_assoc) {
            ClearExclusive(&isa.bits);
            return;
        }
        newisa.has_assoc = true;
        if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    }
    
    2.3 has_cxx_dtor
    • 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
    2.4 shiftcls
    • 存储着Class、Meta-Class的内存地址信息

    我们可以先看下获取对象所属类的方法,如下:

    - (Class)class {
        return object_getClass(self);
    }
    
    🔽
    
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    🔽
    
    inline Class 
    objc_object::getIsa() 
    {
        return ISA();
    }
    

    最终会调用到ISA()方法里面,代码如下:

    inline Class 
    objc_object::ISA() 
    {
        ASSERT(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);
    #endif
    }
    

    可以看到最终返回的值是(Class)(isa.bits & ISA_MASK);,也验证了刚开始我们所提到的拿到isa之后还需要进行一次位运算(&ISA_MASK)才能计算出Class或者Meta-Class的真实地址。

    ISA_MASK 其实是个宏定义:

    #   define ISA_MASK        0x00007ffffffffff8ULL
    // 对应的二进制值为:
    0b0000000000000000011111111111111111111111111111111111111111111000
    

    可以看到中间有33位为1,isa.bits & ISA_MASK就相当于将前28位和后3位置为0,只留下中间33位不变,这样获取的也就是对象所属Class的内存地址。

    shiftcls赋值是在初始化isa时进行的,在上面介绍初始化isa方法时有看到对shiftcls赋值:newisa.shiftcls = (uintptr_t)cls >> 3;,也就是将对象所属类的地址向右移3位(在取值进行& ISA_MASK时又补足这3位了),赋值给newisa.shiftcls

    2.5 magic
    • 用于在调试时分辨对象是否未完成初始化
    2.6 weakly_referenced
    • 是否有被弱引用指向过,如果没有,释放时会更快

    在介绍weak实现流程的文章中有提到过,设置的代码如下:

    inline void
    objc_object::setWeaklyReferenced_nolock()
    {
     retry:
        isa_t oldisa = LoadExclusive(&isa.bits);
        isa_t newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            sidetable_setWeaklyReferenced_nolock();
            return;
        }
        if (newisa.weakly_referenced) {
            ClearExclusive(&isa.bits);
            return;
        }
        newisa.weakly_referenced = true;
        if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    }
    
    2.7 deallocating
    • 标志对象是否正在释放

    可以在release方法的源码中看到,将deallocating设置为true之后,给该对象发送了dealloc消息,如下:

    ...
    objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
    {
        newisa.deallocating = true;
        if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
    
        __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
    
        if (performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
        }
    ...
    }
    
    2.8 extra_rc
    • 里面存储的值是对象的引用计数减1
    2.9 has_sidetable_rc
    • 标记引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的中

    相关文章

      网友评论

          本文标题:[iOS] isa结构

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