美文网首页iOS基础
iOS-OC底层三:ISA的本质

iOS-OC底层三:ISA的本质

作者: 轰天裂天罗三炮 | 来源:发表于2022-05-24 01:55 被阅读0次

    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的本质

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