iOS-OC底层三:ISA的本质

1.前言

对象在计算机中的存储是无序的,内存中的一段地址空间,有可能表示A类声明的对象,也可能是B类声明的对象。我们根据一个对象,可以找到对象所在的地址空间,在计算机硬盘中,地址空间存储的是0和1。但是计算机是怎么知道0和1所代表的含义?为什么可以根据对象找到自己的属性和方法,还可以找到父类的属性和方法,这就是isa的作用。

我们自定义了一个Person类,没有任何属性和方法,为什么我们可以调用 allocinit 呢 ?

因为Person类继承自NSObject,NSObject里有默认的实现,这个方法是苹果在NSObject中给我们提供的

+ (id)alloc {
    return _objc_rootAlloc(self);
}

那继承自NSOject的类是怎么实现调用NSObject的方法?或者说子类是怎么实现调用父类方法的呢?

在OC中所有对象都有isa,而且是结构体中第一个属性。isa的作用就是将地址空间与类信息相对应起来。那isa是怎么实现的呢?那要研究isa具体包含哪些信息。

2.isa的完整内容

isa是怎么生成的呢?通过查看objc_object结构体,看出是isa是由isa_t的定义,从isa_t的实现代码中可以看出是通过联合体(union)定义的。

838中OC对象与isa.png

isa_t在781源码中的表示如下:

union isa_t { //联合体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //提供了cls 和 bits ,两者是互斥关系
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

isa_t在最新的838源码中:苹果对对象的释放又做了优化:

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,它是联合体类型,我们知道了它由两种值:一种是cls,一种是bits。但是它最终的返回值都是Class。

2.1 关于Class

为什么isa的类型是Class?其根本原因是由于isa 对外反馈的是类信息,isa返回时做了一个类型强制转换。可以通过如下代码理解:

isa类型强制转换.png

在最新的838源码中:苹果给isa加上了索引。

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

//ISA方法中会调用如下方法
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}

在不支持isa索引化的情况下,通过getClass获取isa

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

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren't security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}

小结:苹果最新的方向是用索引代替mask,面具方式即将退出舞台。应该是出于扩展性的考虑,用classForIndex(indexcls)这种方式,不再局限于位运算。

2.2 关于ISA_BITFIELD

isa_t的联合体中,有ISA_BITFIELD这个内容,它是以一个宏定义的方式来表示:
在781的代码中是下面:shiftcls分别是arm64下33位,x86_64下44位

isa位域含义.png

在最新的838中:deallocating移出到isa_t结构体中。


838源码中的ISA_BITFIELD.png
  • nonpointer(存储在第0字节) : 是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa。

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

  • has_cxx_dtor(存储在第2个字节): 析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象。

  • shiftcls :(存储在第3-35字节)存储类的指针,类的地址, 即类信息。其实就是优化之前 isa 指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。arm64-not-e中有52位。

  • magic(存储在第36-41字节):判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced(存储在第42字节):对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。

  • deallocating(存储在第43字节):标志对象是否正在释放内存。处理逻辑移到了isa_t中。

  • has_sidetable_rc(存储在第44字节):判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

  • extra_rc(存储在第45-63字节。):存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc 的值就为 9。

2.3 isa的初始化代码

我们知道isa与类进行关联是通过obj->initInstanceIsa

在838的源码中:根据是否有析构函数,有两个objc_object::initInstanceIsa方法

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initInstanceIsa(Class cls, bool)
{
    initIsa(cls);
}

如果没有析构函数,默认按false执行

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

所以isa的创建和最终各个位的赋值由以下objc_object::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;
    }

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

2.4关于验证shiftcls位域中存储的类信息

有以下几种验证方式:

  • 【方式一】通过initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;验证
  • 【方式二】通过isa指针地址ISA_MSAK 的值 & 来验证
  • 【方式三】通过runtime的方法object_getClass验证
  • 【方式四】通过位运算验证

你可能感兴趣的:(iOS-OC底层三:ISA的本质)