ObjC-Runtime源码注疏(二)

2020-10-09  本文已影响0人  一张懵逼的脸

前言

在上一片注疏(ObjC-Runtime源码注疏(一))中,我们已经完整介绍了isa指针的源码,而从本篇幅开始,就开始完整介绍OC对象相关的源码了。
现在我们就要再次从源码的角度去看看OC中的对象objc_object。

objc_object的原码与isa的原码一样,都定义在了objc-private.h中。考虑到objc_object的原代码很长,我们将在下面分段进行说明。
另外,了解OC的同学都知道,objc_object当中isa指向的类型就是objc_class对象,那为啥我们不先介绍objc_class呢?
那是因为在objc-runtime-new.h中,objec_class实际上是objc_object的子类,所以,我们才要先介绍objc_object。

定义&初始化

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();
    
    uintptr_t isaBits() const;

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);

    ......
}

从以上定义我们可以看到,objc_object是一个结构体。它拥有一个isa指针。同时,还拥有一大堆与isa相关的方法和一大堆的初始化方法。

// initIsa() should be used to init the isa of new objects only.

从代码的注释中,我们看到initIsa()方法就是在生成一个新对象时调用初始化isa的。那我们就从这个函数开始吧。
我们先来看看initIsa(Class cls)函数的定义和实现吧。

//声明
void initIsa(Class cls /*nonpointer=false*/);

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

//真正的调用函数
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;
    }
}

从上面的代码段可以看到initIsa(Class cls,bool nonpointer,ool hasCxxDtor)就是初始化isa最终的执行函数。
函数的入参分别是Class,即objc_class的指针;第二个参数为bool值,标识是否为taggedpointer;第三个参数为bool值,标识是否含有C++或者OC的析构函数。第二和第三两个如参默认都是false。

进入函数的第一行就是一个断言,通过isTaggedPointer()函数来判断当前isa是不是一个taggedPointer类型的指针。我们继续深挖isTaggedPointer()的实现源码

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

...

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

...

define _OBJC_TAG_MASK (1UL<<63)

通过不断的深挖,我们得知最终负责判断当前isa是不是一个taggedpointer的函数是_objc_isTaggedPointer(const void * _Nullable ptr)。
其内部实现就是通过_OBJC_TAG_MASK掩码来获得其结果。而_OBJC_TAG_MASK掩码是左移63位,也就是说移动到最高位。结合前面isa结构体的介绍,最高位实际上就是nonpointer的标记位。
至此,我们就清楚了。系统就是通过判断isa的nonpointer位来判断当前isa是不是一个taggedpointer类型的指针。

我们继续回到initIsa(Class cls, bool nonpointer, bool hasCxxDtor)函数的源码。
断言之后,直接就是一个针对入参nonpointer的判断,如果不是nonpointer类型,则直接对isa变量赋值,且初始化的类型就是isa指向的Class对象的地址。
如果是nonpointer类型,则仍旧先进入两个断言,这两个断言实际是判断当前版本是否可以支持nonpointer类型的Isa指针。以及是否已经有了实例的原始类型。
在两个断言都未触发的情况下,执行下一系列代码:

  1. 用0值初始化一个新的isa指针 -> newisa
  2. 判断是否支持indexed_isa,这部分先往后放一放
  3. 对bits用ISA_MAGIC_VALUE进行赋值。ISA_MAGIC_VALUE的掩码值为0x001d800000000001ULL,按照64位分解可得00000000-0-0-0-111011-00000000000000000000000000000000000000000000-0-0-1,此处应注意到iOS小端字节序列,高位在右侧。此时我们再对照x86的isa指针内部位的分布图来对照可知,ISA_MAGIC_VALUE最终将nonpointer位赋值为1,即当前是一个nonpointer指针,之后在image字段上赋值,这个值应该与后面的编译器相关,此处就不再展开了。
  4. 对isa的has_cxx_dtor字段赋值,同时将传进来的cls的shiftcls字段指向isa的shiftcls字段的位置
  5. 将newisa赋给isa
    至此,我们就完成了isa的初始化工作。

下面,我们说说刚才跳过的indexed_isa的支持的宏判断

#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

从SUPPORT_INDEXED_ISA宏的定义,就可以看到,其实是否支持indexed_isa与当前设备的CPU架构有直接关系。
从宏的判断,我们可以猜测如下:

  1. "ARM_ARCH_7K >= 2" 与AppleWatch的CPU型号有关,其版本要>=2
  2. "arm64" 是判断当前系统是否为arm64位
  3. "!LP64" 即系统不能是LP64。所谓LP64,指的是int类型为32位,long类型为64位,pointer类型也为64位才被称为LP64

所以说,从以上来看,我们的iphone手机是不支持indexed_isa结构的。

后面我们再看看剩余的几个init方法:

    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

void initClassIsa函数

inline void 
objc_object::initClassIsa(Class cls)
{
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}

从该函数实现上看,如果当前runtime不支持nonpointer 或者 根据cls->instancesRequireRawIsa()的返回值为真,则直接调用initIsa方法,只不过nonpointer参数为false;负责还是调用initIsa函数,只不过nonpointer为true。
对于DisableNonpointerIsa变量,我们可以看到如下定义:

//objc-env.h
OPTION( DisableNonpointerIsa,     OBJC_DISABLE_NONPOINTER_ISA,     "disable non-pointer isa fields")

这块的代码就是从当前环境中为DisableNonpointerIsa赋值,即代表当前runtime环境是否支持nonpointerisa。
对于instancesRequireRawIsa()方法,我们能看到如下定义:

    #define FAST_CACHE_REQUIRES_RAW_ISA   (1<<13)

    ...
    
    bool instancesRequireRawIsa() {
        return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
    }

可以看到从cache读取第13位作为返回值。至于cache是什么,我们后面还会提及。

initProtocolIsa函数

inline void
objc_object::initProtocolIsa(Class cls)
{
    return initClassIsa(cls);
}

从代码中可以看到,对于protocol协议对象,直接调用了initClassIsa函数,没有其他操作

initInstanceIsa函数

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

    initIsa(cls, true, hasCxxDtor);
}

从实现中看到,调用initInstanceIsa时,nonpointer直接为true,所以这个函数必然是支持初始化nonpointer指针的。

最后,我们回过头来,去看一看objc_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();
    
    uintptr_t isaBits() const;

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
}

函数返回值就是一个objc_class的结构体。函数在内部仍旧先判断是否为taggedpointer指针,如果是直接就中断。之后又通过SUPPORT_INDEXED_ISA宏来定义了不同的执行逻辑。之前已经说过SUPPORT_INDEXED_ISA与设备相关,一般的iphone是不支持的。所以只有最后一句被执行。

return (Class)(isa.bits & ISA_MASK);

这句实际上就是直接将isa.bits中的44位(x86)或33位(arm64)保存的地址强转成Class而已。

rawISA()函数

inline Class
objc_object::rawISA()
{
    ASSERT(!isTaggedPointer() && !isa.nonpointer);
    return (Class)isa.bits;
}

从源码可知,不是taggedpointer并且nonpointer位也不能是1的情况下,直接返回isa.bits;

getIsa()函数

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

首先,代码中又出现了些心的东西,如fastpath和slowpath。这两个是什么的东西?
经过查找,我们看到了如下定义:

//objc-os.h
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

//llvm-DenseMap.h
#define LLVM_UNLIKELY slowpath
#define LLVM_LIKELY fastpath

在代码中找到两组定义,但感觉其实都差不多。对于__builtin_expect()函数,解释如下:
__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1)表示 x 的值为真的可能性更大;
__builtin_expect((x),0)表示 x 的值为假的可能性更大。
说白了就是fashpath(x)代表x很可能为真,slowpath(x) 代表x很可能为假。要求编译器优先处理判断各自分支。这里说的优先判断,可以理解为预先读取下一条指令。

llvm-DenseMap.h中的定义,unlikely和likely实际上与之前的定义也是一样的。

所以代码中的fastpath(!isTaggedPointer()),其含义就是大概率当前判断的对象不是一个taggedpoiner对象。

回到getIsa()函数本身。首先判断了当前isa如果不是taggedpointer,则直接调用ISA()函数返回。

如果是taggedpointer,则首先声明了slot变量,同时,将当前isa赋值给ptr。
其次,通过(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK 计算出一个值,从下面的代码看slot的值是一个下标。
其中,_OBJC_TAG_SLOT_SHIFT的值为60,也就是说ptr右移60,这样就留出了地址的高四位。然后在与上_OBJC_TAG_SLOT_MASK就得出了slot的值。
再次,使用该slot下标在objc_tag_classes数组中获取对应的class指针。
那么,我们再看看objc_tag_classes是什么。

extern "C" { 
    extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];
    extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes

...

#define _OBJC_TAG_SLOT_COUNT 16
#define _OBJC_TAG_EXT_SLOT_COUNT 256

结合代码我们可以这样理解。实际上在系统中已经定义好了所有taggedpointer的类型。对于所有taggedpointer对象的isa中是不存储对应的对象类型的,而是通过isa头4位来计算出对应类型的下标,然后从系统定义的taggedpointer类型中获取对应的Class类型。这样做的好处就是扩充了taggedpointer对象isa内存储值的空间。
从上面的定义来看,系统定义了16种类型。而对于用户自己扩展的类型,给了一个256大小的空间用来存储用户自定义出来的taggedpointer对象类型。

好了,再次回到getIsa的代码中,紧接着通过slowpath函数优化了判断语句,即大概率不会是系统不认识的taggedpointer类型。如果真的是不认识的,就说明是用户自定义扩展的,那么直接走前面说过的用户自定义数组objc_tag_ext_classes来获取。
最终,返回对应的Class,getIsa()方法结束

至此,我们把objc_object结构体中的前半段函数都介绍了一遍,我们已经高清楚objc_object在声明后是如何初始化自己的isa的。下一章节,我们再看看objc_object里的其他函数。

上一篇下一篇

猜你喜欢

热点阅读