美文网首页
OC Runtime 底层类结构2020最新

OC Runtime 底层类结构2020最新

作者: 扬仔360 | 来源:发表于2020-07-16 17:01 被阅读0次

    PS:在跟着看今年的WWDC2020所有的session,发现了很多更新,例如 objc_setHook_setAssociatedObjectset入口函数做了一层下沉,以及等等等等,最大的一个更新就是出现了 class_rw_ext_t, 以前只有 ro rw,现在多了一个rwe。

    runtime的代码,一点只用 API,不要写这些底层结构,但是一定要了解,因为你的API是随时可用的,如果你从 ro 里面读取 protocolspropertiesmethods,那样一旦底层结构变了,代码就得更新了。

    再次梳理了目前最新的 OC 类结构。以便同事们参考。

    整体代码基于目前官方最近开源的OC底层源码版本objc4_781,主要研究 objc-runtime-new.h 文件,runtime 这部分包:

    1. OC 的声明后的实现部分开源。
    2. 底层调用的 C 和 C++ 开源。
    3. 汇编语言.mm,Apple 对于一部分CC++无法实现的功能使用,例如msgSend

    整个逻辑关系图如下

    类结构大手稿

    1. objc_object 「万物之源」

    连类的结构都是基于 objc_object 的,所以说 OC 里面一切皆对象呢。

    objc_object

    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    1.1 id 和 isa

    id 的本质其实就是一个 objc_object * 的结构体指针

    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    在结构体内部只有一个 Class 结构的 isa

    isa 是什么?isa 的本质是 isa_t,在另外一个文件 objc-private.h 中有 isa_t 的定义,是一个联合体,也叫共用体,可以参考这篇文章讲解共用体

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

    这个里面存储两个,Class clsuintptr_t bits。因为是联合体,所有实际只能有一个出现,ISA_BITFIELD是真正存储值的东西,它存储于文件 isa.h,根据平台不同而不同,下面是 arm64 版本的:

    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; /*0:代表普通指针,1:表示优化过的,可以存储更多信息。*/\
          uintptr_t has_assoc         : 1; /*是否设置过关联对象。如果没设置过,释放会更快*/\
          uintptr_t has_cxx_dtor      : 1; /*是否有C++的析构函数*/\
          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; /*引用计数器是否过大无法存储在ISA中。如果为1,那么引用计数会存储在一个叫做SideTable的类的属性中*/\
          uintptr_t extra_rc          : 19; /*里面存储的值是引用计数器减1*/\
    
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    

    这个 ISA_MASK 这种的就是一个蒙版,isa 完整的值通过跟他进行与操作,就只留下了class的地址,(Class)(isa.bits & ISA_MASK)

    image.png

    话外一提,objc_object 的结构除了上面这个结构,根据这个结构存的值,提供了一整套的API方法,存储于 objc_private.h 文件中。

    那么 Class 是什么呢?看2.0

    2. class 结构体:objc_class

    Classobjc_class * 结构体指针。

    objc_class

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
        class_rw_t *data() const {
            return bits.data();
        }
    }
    

    这里先后存储的东西分四个:

    1. 继承来的 isa 结构的指针,占8字节
    2. superClass 的 Class 指针,占8字节
    3. cache类型,本篇跳过。
    4. class_data_bits_t类型的bits,下段落展开。

    3. 类中数据 class_data_bits_t

    class_data_bits_t 里存储着当前类的很多信息,通过 class_data_bits_t 进行蒙版运算 (bits & FAST_DATA_MASK) 可以算出 class_rw_t,简称就是 rw
    下一段落重点讲解 class_rw_t

    class_data_bits_t ==> class_rw_t ==> class_ro_t
    class_data_bits_t ==> class_rw_t ==> class_rwe_t
    

    这个关系是 2020 年新的变动,作为OC Swift的开发者一点要了解,具体看这段 WWDC Session

    class_data_bits_t

    struct class_data_bits_t {
        friend objc_class;
        // Values are the FAST_ flags above.
        uintptr_t bits;
    public:
    
        class_rw_t* data() const {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
        void setData(class_rw_t *newData)
        {
            ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
             //这个过程不需要加锁处理,因为仅在实现或构造期间进行 Set 操作
             //使用 store-release fence 操作,类似一种轻量级的资源锁,
             //如下面的`atomic_thread_fence `,因为可能会同时存在数据的读写
            uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
            atomic_thread_fence(memory_order_release);
            bits = newBits;
        }
    
    //获取类的ro数据,即使在构建过程中。
    //这个需要修复,至少在没有编译器屏障的情况下,并不是真正安全的。
    //在 实现类 更改数据字段的时候,可能没有内存屏障的。
        const class_ro_t *safe_ro() {
            class_rw_t *maybe_rw = data();
            if (maybe_rw->flags & RW_REALIZED) {
                // maybe_rw is rw
                return maybe_rw->ro();
            } else {
                // maybe_rw is actually ro
                return (class_ro_t *)maybe_rw;
            }
        }
    }
    

    上面代码提到了内存屏障,那么什么是内存屏障,这个概念涉及到代码的乱序执行,扩展里面单独说。这个注释的意思是说,imageLoader的时候,还在实现这个类的过程中,不要读取ro数据,因为还没有构建完成。

    ro rw rwe 是相互依存的表述类结构的结构,ro 是在内存中直接读取出来的,只读readonly的,其中包括了我们熟悉的ivar,所以说ivar是不可以动态添加的,就是这个原因。

    3.1 类数据演变出:class_ro_t

    class_ro_t

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
        _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                return _swiftMetadataInitializer_NEVER_USE[0];
            } else {
                return nil;
            }
        }
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    
        class_ro_t *duplicate() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
                return ro;
            } else {
                size_t size = sizeof(*this);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                return ro;
            }
        }
    };
    

    其中包含 const ivar_list_t * ivars 用来表示 ivar_list_tivar_t

    3.1.1ivar_list_tivar_t

    ivar_list_t 通过 entsize_list_tt 存储了 ivar_t

    entsize_list_tt 是提供遍历器的通用容器,详细见我博客的另一篇文章,Apple源码用到的一些数据结构

    ivar_list_t

    struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
        bool containsIvar(Ivar ivar) const {
            return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
        }
    };
    

    ivar_t

    struct ivar_t {
    #if __x86_64__
        // *offset was originally 64-bit on some x86_64 platforms.
        // We read and write only 32 bits of it.
        // Some metadata provides all 64 bits. This is harmless for unsigned 
        // little-endian values.
        // Some code uses all 64 bits. class_addIvar() over-allocates the 
        // offset for their benefit.
    #endif
        int32_t *offset;
        const char *name;
        const char *type;
        // alignment is sometimes -1; use alignment() instead
        uint32_t alignment_raw;
        uint32_t size;
    
        uint32_t alignment() const {
            if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
            return 1 << alignment_raw;
        }
    };
    

    3.2 类数据演变出:class_rw_t

    rw 是可读可写的意思,runtime 动态增加方法等就是通过对 rw 进行修改写的操作完成的。

    这里先看他的结构定义,对全局有了解。这里后续章节「。。。」详细展开更细致的研究。

    class_rw_t

    struct class_rw_t {
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
        Class firstSubclass;
        Class nextSiblingClass;
    public:
        class_rw_ext_t *deepCopy(const class_ro_t *ro);
        const method_array_t methods();
        const property_array_t properties();
        const protocol_array_t protocols();
    };
    

    3.3 类数据演变出:class_rw_ext_t

    三者的演变关系,我粘贴一段类构造过程的源码:如下:

    // at file `objc-runtime-new.mm` static void methodizeClass(Class cls, Class previously)
        auto rw = cls->data();
        auto ro = rw->ro();
        auto rwe = rw->ext();
    

    但是 ro 其实才是一直不变的,也叫 clean memory,RW 是跟随runtime去操作变化的,叫做 Dirty Memory。

    rwe 是什么其实很简单,2020年苹果出了iOS14,其SDK里面把rw中不常用的一部分砍了出去,变成了RWE,官方说,用 Mac 的 MailApp 做测试,Dirty Memory从80MB节约到了40MB。

    class_rw_ext_t

    struct class_rw_ext_t {
        const class_ro_t *ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        char *demangledName;
        uint32_t version;
    };
    

    4. method_array_t、 method_list_t、 method_t

    接下来三部分是:

    1. 类的 rw rwe 中的 method_array_t
    2. 类的 rw rwe 中的 property_array_t
    3. 类的 rw rwe 中的 protocol_array_t

    这三个的继承数据结构都是:list_array_tt,详情可见我博客的另一篇文章,Apple源码用到的一些数据结构

    通过 rw rwe 中的 method_array_t 获取 methods,它的类型定义如下:

    method_array_t

    class method_array_t : 
        public list_array_tt<method_t, method_list_t> 
    {
        typedef list_array_tt<method_t, method_list_t> Super;
    
     public:
        method_array_t() : Super() { }
        method_array_t(method_list_t *l) : Super(l) { }
    
        method_list_t * const *beginCategoryMethodLists() const {
            return beginLists();
        }
        
        method_list_t * const *endCategoryMethodLists(Class cls) const;
    
        method_array_t duplicate() {
            return Super::duplicate<method_array_t>();
        }
    };
    

    其中获取到的一个或多个 method_list_t,具体原理移步到上面文章中。

    method_list_t

    // entsize 中的2个字符位用来做 fixup 标记。
    struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
        bool isUniqued() const;
        bool isFixedUp() const;
        void setFixedUp();
    
        uint32_t indexOfMethod(const method_t *meth) const {
            uint32_t I = 
                (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
            ASSERT(i < count);
            return I;
        }
    };
    

    通过遍历器遍历 method_t

    method_t 就是表示一个方法或者函数(包含:函数名、返回值、参数、函数体)

    method_t

    struct method_t {
        SEL name; // 方法名称
        const char *types; // 编码(返回值类型、参数类型)
        MethodListIMP imp; // 方法的地址/实现
    
        struct SortBySELAddress :
            public std::binary_function<const method_t&,
                                        const method_t&, bool>
        {
            bool operator() (const method_t& lhs,
                             const method_t& rhs)
            { return lhs.name < rhs.name; }
        };
    };
    

    SortBySELAddress()方法是方便函数进行排序,其实在 methodList中 函数是根据SEL name这个对象的内存地址进行排序的,后续寻找方法的时候,会用二分查找增加查找效率。

    这里也是今年的一个变动,获取方法的 MethodListIMP 直接从sel里面内存平移8个字节就可以得到。

    4.1 method_t 方法的数据结构

    SEL是一个指向objc_selector 结构体的指针:
    typedef struct objc_selector *SEL;

    获取一个SEL的方法

        SEL sel1 = @selector(selector);
        SEL sel2 = sel_registerName("selector");
        SEL sel3 = NSSelectorFromString(@"selector");
        
        // SEL 转换成字符串
        char *string1 = sel_getName(sel1);
        NSString *string2 = NSStringFromSelector(sel1);
    

    IMP 是指向方法实现提的函数指针,调用一个方法,本质上就是一个方法寻址的过程,通过SEL寻找IMP。
    method_t,就是在两者之间做映射关系

    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
    #endif
    

    4.2 Type Encodings

    Type Encodings 就是一个编码,runtime 中对于 method_t 的返回值和参数,使用了Type Encodings 字符串来进行描述:
    @encode()指令可以将类型转换为 Type Encodings 字符串编码:

    char *buf1 = @encode(int **);
    char *buf2 = @encode(struct key);
    char *buf3 = @encode(Rectangle);
    

    OC 方法都有两个隐式参数,方法调用者 (id)self 和方法名 (SEL) _cmd ,所以我们才能在方法中使用 self_cmd

    -(void)test,它的编码为 “v16@0:8”,可以简写为 “v@:”
    v:代表返回值类型为 void
    16:代表所有参数所占的总字节数
    @:代表参数 1 类型为 id
    0:代表参数 1 从第几个字节开始存储
    ::代表参数 2 类型为 SEL
    8:代表参数 2 从第几个字节开始存储

    具体这里在官网上有详细解释,See Type Encodings in Objective-C Runtime Programming Guide

    5. property_array_t、 property_list_t、 property_t

    property_array_t

    class property_array_t : 
        public list_array_tt<property_t, property_list_t> 
    {
        typedef list_array_tt<property_t, property_list_t> Super;
    
     public:
        property_array_t() : Super() { }
        property_array_t(property_list_t *l) : Super(l) { }
    
        property_array_t duplicate() {
            return Super::duplicate<property_array_t>();
        }
    };
    

    property_list_t

    struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
    };
    

    property_t

    struct property_t {
        const char *name;
        const char *attributes;
    };
    

    6. protocol_array_t、 protocol_list_t、 protocol_t

    protocol_array_t

    class protocol_array_t : 
        public list_array_tt<protocol_ref_t, protocol_list_t> 
    {
        typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;
    
     public:
        protocol_array_t() : Super() { }
        protocol_array_t(protocol_list_t *l) : Super(l) { }
    
        protocol_array_t duplicate() {
            return Super::duplicate<protocol_array_t>();
        }
    };
    
    

    protocol_list_t

    struct protocol_list_t {
        // count is pointer-sized by accident.
        uintptr_t count;
        protocol_ref_t list[0]; // variable-size
    
        size_t byteSize() const {
            return sizeof(*this) + count*sizeof(list[0]);
        }
    
        protocol_list_t *duplicate() const {
            return (protocol_list_t *)memdup(this, this->byteSize());
        }
    
        typedef protocol_ref_t* iterator;
        typedef const protocol_ref_t* const_iterator;
    
        const_iterator begin() const {
            return list;
        }
        iterator begin() {
            return list;
        }
        const_iterator end() const {
            return list + count;
        }
        iterator end() {
            return list + count;
        }
    };
    

    protocol_t

    struct protocol_t : objc_object {
        const char *mangledName;
        struct protocol_list_t *protocols;
        method_list_t *instanceMethods;
        method_list_t *classMethods;
        method_list_t *optionalInstanceMethods;
        method_list_t *optionalClassMethods;
        property_list_t *instanceProperties;
        uint32_t size;   // sizeof(protocol_t)
        uint32_t flags;
        // Fields below this point are not always present on disk.
        const char **_extendedMethodTypes;
        const char *_demangledName;
        property_list_t *_classProperties;
    
        const char *demangledName();
    
        const char *nameForLogging() {
            return demangledName();
        }
    
        bool isFixedUp() const;
        void setFixedUp();
    
        bool isCanonical() const;
        void clearIsCanonical();
    
    #   define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f))
    
        bool hasExtendedMethodTypesField() const {
            return HAS_FIELD(_extendedMethodTypes);
        }
        bool hasDemangledNameField() const {
            return HAS_FIELD(_demangledName);
        }
        bool hasClassPropertiesField() const {
            return HAS_FIELD(_classProperties);
        }
    
    #   undef HAS_FIELD
    
        const char **extendedMethodTypes() const {
            return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil;
        }
    
        property_list_t *classProperties() const {
            return hasClassPropertiesField() ? _classProperties : nil;
        }
    };
    

    6.isa 走位图

    这个图解释网上太多了,没什么好说的,放这里,跳过不讲了。

    isa 走位图

    拓展研究

    内存屏障 memory barrier

    上面源码中 class_ro_t *safe_ro() 的注释里提到了一个的 compiler barrier,编译器屏障,这个是为了解决代码执行问题的。

    指令重排是指:导致的代码乱序执行。举例来说:如果第一个语句涉及内存读取,第二个语句只是简单的指令计算,那么CPU为了提高效率,完全可能在第一个语句的内存读取完成之前,先执行完了第二个语句。

    上下两条指令没有相关性,就可能发生。如果涉及一个值,就有可能发现。
    另外一个概念叫做 as if serial,重排序的原则是看上去像是顺序执行的,不影响单线程的最终一致性。

    但是多线程是不保证一致性的。所以重排序是CPU的公共特性。

    然后就出现了,内存屏障 memory barrier
    屏障上下的指令不会发生重排序,在汇编层面,通过 lock 汇编指令完成,lock是锁总线,CPU访问内存的总线,等指令访问完了,其他CPU才能去访问。

    lock 本身的前面的所有指令全部不能越过 lock 重排序。

    拓展阅读:Linux内核同步机制之(三):memory barrier

    GCD 里面的 barrier 栅栏函数,等待所有位于栅栏函数之前的操作执行完毕后执行,跟这个命名类似,有相同也有不同,相同就是都是为了保证因为多线程和并发导致的前后指令/任务的乱序。

    内存屏障是指令级别的,在CPU汇编指令级别上的。栅栏函数是GCD级别的,GCD又是iOS系统级别的机制。

    References

    1. objc4_781 官方源码
    2. 简图记录-android fence机制
    3. Fence和非原子操作的ordering
    4. C语言共用体(C语言union用法)详解
    5. 「注意这个已经过时了,新版本不是这样的」补充git上找到的一个 o_t c_t ro rw 的连通图,缺点是没有rwe,名字我自己瞎起的,我后面画图会参考这个。


      o_t c_t ro rw 连通图

    相关文章

      网友评论

          本文标题:OC Runtime 底层类结构2020最新

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