美文网首页
ObjC-Runtime源码注疏(二)

ObjC-Runtime源码注疏(二)

作者: 一张懵逼的脸 | 来源:发表于2020-10-09 15:47 被阅读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里的其他函数。

    相关文章

      网友评论

          本文标题:ObjC-Runtime源码注疏(二)

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