美文网首页
03 isa探究

03 isa探究

作者: lcd357287797 | 来源:发表于2021-01-22 10:49 被阅读0次

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的定义中引出了一个新的数据类型--联合体。我们来看下联合体和结构体有什么区别:

结构体

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

  • 优点存储容量大,包容性强,且成员之间不会相互影响
  • 缺点:所有变量都会分配内存,内存消耗较大
联合体

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

  • 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间
  • 缺点包容性差
    我们再回过来看isa_t的定义,其定义了两个变量一个为bits,另一个为cls,同时如果定义了ISA_BITFIELD的情况下,定义了位域结构体以便我们精确的操作isa_t中的不同位的数据。ISA_BITFIELD的定义根据不同的cpu进行了不同的定义,定义的字段和内容大同小异,只是不同的字段占用的位数量不一样,本文仅以x86_64为例说明:
      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下的定义,其中:

  • nonpointer:存储于第0位,标识是否为优化isa标志。0代表不是优化的isa,此isa是一个纯指向类或者原类的指针;1标识是优化的isa,此isa中包含类信息,对象的引用计数等。我们现在创建的对象中的isa基本上都是优化后的isa

  • has_assoc:存储于第1位,关联对象标志位。对象含有或者曾经含有关联引用。0表示没有;1表示有。没有关联引用的对象可以更快的释放内存。

  • has_cxx_dtor:存储于第3位,析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速的释放对象。

  • shiftcls:存储于第3-46位,存储类的信息,这个是我们主要关注的内容

  • magic:存储于第47-52位,判断对象是否初始化完成,是调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced:存储于第53位,标识对象是否被指向或者曾经被指向一个ARC的弱引用变量。如果没有弱引用的对象可以更快的被释放。

  • unused:存储于第54位。

  • has_sidetable_rc:存储于第55位,判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

  • extra_rc:存储于第56-63位,存放该对象引用计数值-1的结果

    ISA_BITFIELD

了解了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

相关文章

  • 03 isa探究

    iOS开发者一定知道每个实例对象都有一个isa指针,其中存储着对象的类信息。今天我们就来探究下isa是如何保存类的...

  • iOS--OC底层原理文章汇总

    OC底层原理01—alloc + init + new原理OC底层原理02—内存对齐OC底层原理03— isa探究...

  • 类的分析、探索

    类的isa探究 准备研究类LGPerson类。沿用探究oc对象的思路,使用lldb先从类的内存、isa来进行观察。...

  • OC对象探究03:isa 解析

    提前准备 为了探究对象在底层的实现方式,此次使用clang来进行探究。相关语句clang -rewrite-obj...

  • OC底层原理03— isa探究

    对象的本质 在分析isa前,先分析一下我们常见的解除最多的——对象。为了探究OC对象的本质是什么,就有必要了解Cl...

  • iOS底层-isa结构(isa_t)

    在iOS 底层-- isa指向探究中探索了isa的指向,那么isa的结构具体是什么样的。从源码中来着手研究。 一、...

  • iOS底层 - cache原理分析

    iOS开发底层探究之路 在对Objective-C底层的探究过程中,已经探究过objc_class 结构中的isa...

  • iOS底层原理探究05-类的底层原理isa链&继承链&类的内存结

    isa指向分析 通过《iOS底层原理探究04-OC对象的本质&联合体位域&isa分析》[https://www.j...

  • 2019-03-02

    Runtime Objective-C Runtime iOS底层原理探究-Runtime isa 和 Class...

  • 探究isa指针本质

    和之前一样,也是从源码中找到答案。我们都知道,实例对象的isa指针指向类对象,类对象的isa指针指向元类对象。 1...

网友评论

      本文标题:03 isa探究

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