03 isa探究

2021-01-22  本文已影响0人  lcd357287797

iOS开发者一定知道每个实例对象都有一个isa指针,其中存储着对象的类信息。今天我们就来探究下isa是如何保存类的信息的。
通过objc的源码可以找到我们在调用alloc方法创建实例对象的时候有初始化isa的操作,其初始化代码如下

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

由上述代码可知,isa其实是isa_t类型的结构体,那么isa_t又是什么呢?

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

从isa_t的定义中引出了一个新的数据类型--联合体。我们来看下联合体和结构体有什么区别:

结构体

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

联合体

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

      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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

如上所示就是ISA_BITFIELD在x86_64下的定义,其中:

了解了isa的数据类型之后我们看下objc是如何设置isa的呢?根据objc源码我们找到了设置isa的具体方法initIsa,代码如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    isa = newisa;
}

在实际开发中,我们创建的类基本上都是nonpointer的,故此处isa的创建会走else中的代码,在此处打上断点后我们一步一步跟断点。


ISA_MAGIC_VALUE

经过ISA_MAGIC_VALUE初始化newisa.bit之后,我们看到newisa中bits的值变为了8303511812964353,而ISA_MAGIC_VALUE转为10进制数也是8303511812964353,在计算器中输入后可以看到2进制计数法中每一位的情况,如下所示

ISA_MAGIC_VALUE

newisa的位信息中,除了nonpointer为1与计算器中的第0位的1相符之外,magic被置为了59,同时计算器中从第47位开始(即magic的首位)显示为110111,此时我们将59输入计算器中发现59的二进制计数正是110111,与bit中的值相等

59

下一步是setClass,我们进入setClass方法后发现设置shiftcls的代码就一行。

    shiftcls = (uintptr_t)newCls >> 3;

此处将class右移了3位存入了shiftcls中,这是因为class信息的最后4位始终是0,即无效位,为了匹配shiftcls是从第3位开始存储的,便去除了最右边三位无效位存入了shiftcls中

setClass

在setClass之后shiftcls的信息变成了536875037,那么此时shiftcls中存储的是否是SLPerson的类信息呢?
我们通过以下几种方式来看下:

  1. 我们刚刚说了class的最后三位是0,所以我们只需要将isa中除了shiftcls的其它位置0便可以得到正确的类信息,即我们可以采取右移3位,左移20位,再右移17位的方式将其余位全部置0。
(lldb) p newisa.bits >> 3
(uintptr_t) $53 = 1037939513495581
(lldb) p $53 << 20
(uintptr_t) $54 = 562954278797312
(lldb) p $54 >> 17
(uintptr_t) $55 = 4295000296
(lldb) po $55
SLPerson
运算过程

通过如上打印可以看到我们将其余位全部置0后得到的信息就是SLPerson。
这是我们直接通过位运算来还原了isa中类的信息,那么系统又是如何获取类的信息的呢?

- (Class)class {
    return object_getClass(self);
}

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

inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}

inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;
    clsbits &= ISA_MASK;

    return (Class)clsbits;
#endif
}

#   define ISA_MASK        0x00007ffffffffff8ULL

从源码可知,获取class的方法最终就是一行代码return bits & ISA_MASK,我们将ISA_MASK放入计算器中发现就是一个3-46位为1,其余位为0的二进制数。与bits按位进行&运算最终的结果和我们进行位移运算是一样的~

ISA_MASK
上一篇 下一篇

猜你喜欢

热点阅读