美文网首页
iOS Class实现原理-isa

iOS Class实现原理-isa

作者: 野码道人 | 来源:发表于2021-02-01 02:43 被阅读0次

    本文会阐述下面几个问题

    1、isa是什么
    2、isa的内存布局
    3、Class与isa背后的设计

    查看源码(源码版本objc4-781.2)

    源码地址
    打开objc-private.h查看源码,发现isa是一个联合体,联合体各个成员变量之间共享内存,所以isa占8个字节

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    

    扒开这个宏定义ISA_BITFIELD

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    #   define ISA_BITFIELD                                                        \
          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 deallocating      : 1;                                         \
          uintptr_t has_sidetable_rc  : 1;                                         \
          uintptr_t extra_rc          : 8
    #   define RC_ONE   (1ULL<<56)
    #   define RC_HALF  (1ULL<<7)
    
    # else
    #   error unknown architecture for packed isa
    # endif
    

    如上isa_t联合体在arm64和x86的语义字段完全相同并且都占用8个字节,但是内存布局存在很大差异,本篇文章会以arm64为例展开介绍,所以isa_t长这样,有三个成员变量cls,bits,和一个匿名结构体,三者共享一块内存

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
        struct {
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
        };
    };
    
    从Class的定义开始探究isa
    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);
    
        bool hasNonpointerIsa();
        bool isTaggedPointer();
        bool isBasicTaggedPointer();
        bool isExtTaggedPointer();
        bool isClass();
    
        // object may have associated objects?
        bool hasAssociatedObjects();
        void setHasAssociatedObjects();
    
        // object may be weakly referenced?
        bool isWeaklyReferenced();
        void setWeaklyReferenced_nolock();
    
        // object may have -.cxx_destruct implementation?
        bool hasCxxDtor();
        ...
    };
    

    我们来详细的看下里面的函数源码,首先看下isa的初始化函数,如下

    inline void 
    objc_object::initIsa(Class cls)
    {
        initIsa(cls, false, false);
    }
    
    inline void 
    objc_object::initClassIsa(Class cls)
    {
        if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
            initIsa(cls, false/*not nonpointer*/, false);
        } else {
            initIsa(cls, true/*nonpointer*/, false);
        }
    }
    
    inline void
    objc_object::initProtocolIsa(Class cls)
    {
        return initClassIsa(cls);
    }
    
    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        ASSERT(!cls->instancesRequireRawIsa());
        ASSERT(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    

    最终都会调用到objc_object::initIsa函数,精简定义如下,省略了断言和条件编译不生效的部分

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    {     
        if (!nonpointer) {
            isa = isa_t((uintptr_t)cls);
        } else {
            isa_t newisa(0);
            newisa.bits = ISA_MAGIC_VALUE;
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.shiftcls = (uintptr_t)cls >> 3;
    
            isa = newisa;
        }
    }
    

    我们来大概翻译下这个函数,nonpointer占一个二进制位,用来标识内存是否是64位系统的布局,if 0函数直接赋值为cls地址,else初始化一个isa_t类型的newisa联合体,分别对bits、has_cxx_dtor、shiftcls进行赋值,然后赋值给isa,所以cls是32位系统的类指针,而64位系统是bits通过位运算来获取类指针的

    isa的get函数与初始化函数对应,不再赘述,摘出几处重点说明,Apple的注释还是那么的清晰

    // object may have associated objects?
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();
    
    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();
    
    // object may have -.cxx_destruct implementation?
    bool hasCxxDtor();
    

    hasAssociatedObjects可能有关联对象,isWeaklyReferenced是否有弱引用,hasCxxDtor是否有c++析构函数的实现

    我们还是看下源码,如下isa.nonpointer在64位系统是1,函数的返回值就是isa对应的成员

    inline bool
    objc_object::hasAssociatedObjects()
    {
        if (isTaggedPointer()) return true;
        if (isa.nonpointer) return isa.has_assoc;
        return true;
    }
    
    inline bool
    objc_object::isWeaklyReferenced()
    {
        ASSERT(!isTaggedPointer());
        if (isa.nonpointer) return isa.weakly_referenced;
        else return sidetable_isWeaklyReferenced();
    }
    
    inline bool
    objc_object::hasCxxDtor()
    {
        ASSERT(!isTaggedPointer());
        if (isa.nonpointer) return isa.has_cxx_dtor;
        else return isa.cls->hasCxxDtor();
    }
    
    isa的内存布局

    基于此,我们得出以下结论,重点看下上面的匿名结构体,有9个成员变量,用位域来标识

    • nonpointer
      占一个二进制位,用来标识内存是否是64位系统的布局,0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等
    • has_assoc
      占用一个二进制位,标识对象含有或者曾经含有关联对象
    • has_cxx_dtor
      这一位表示当前对象是否有c++析构函数
    • shiftcls
      占33位,用于存储类地址
    • magic
      占6位,标识当前对象是否已经初始化
    • weakly_referenced
      占1位,标识当前对象是否有弱引用
    • deallocating
      占1位,标识当前对象是否正在释放
    • has_sidetable_rc 和 extra_rc
      分别占1位和19位用于存储优化引用计数
    Class与isa背后的设计

    这里有一篇文章写得很好
    放一张经典图片

    class_isa.png

    有关这张图,网上解释的很清楚了,这里想讨论三个问题

    • Apple为什么要设计meta class这个东西?
      假设没有meta class这个东西我们知道实例对象只存储了成员变量的值和一个isa指针,对象调用方法是通过isa指针找到类,类对象存储了方法列表等一系列通用结构,从而实现了方法继承、缓存、调用等操作,这极大的节约了内存,试问如果每个实例对象都存储一遍方法列表,这将是多大的开销,然而类方法放在哪里呢,我们知道OC方法调用是通过选择器sel找到最终的imp函数指针,从而完成调用过程,那sel是什么呢,就是一个编码过的字符串,如果有同名的类方法和实例方法怎么办,sel相同,我们知道,这是无法通过编译的,因为编译器要做方法唯一性校验,所以我们需要另外一个结构来存储类方法,meta class应运而生

    • 为什么NSObject(根类)的元类的isa指针指向的是根元类自己?
      这个问题可以反过来想,如果根元类的isa指向nil会有什么问题?
      isa指针指向的是自己的抽象工厂对象,自己若存在,则一级一级往上回溯,一定要是个闭环,试问如果访问到某一层的时候发现是个nil,那么自己又是怎么存在的呢

    • 为什么根元类的superclass指针指向NSObject,而NSObject的superclass指向的却是nil?
      NSObject已经是根类了,无从继承了,所以NSObject的superclass指向nil,NSObject是被设计出来的根类,meta class作为类自然要从NSObject派生出来,以具备OC类的结构与各种特性

    最后

    本篇讨论了Class的isa指针,下篇打算讨论下Class的方法查询、转发、缓存等机制

    相关文章

      网友评论

          本文标题:iOS Class实现原理-isa

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