美文网首页iOS开发
Runtime源码解析-类中bits

Runtime源码解析-类中bits

作者: 祀梦_ | 来源:发表于2022-12-12 23:37 被阅读0次

    Runtime源码解析-类中bits

    • 首先我们再看一眼objc_class类的定义,本篇文章研究bits到底存储了哪些信息
    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_data_bits_t结构中
    struct class_data_bits_t {
        friend objc_class;
    
        uintptr_t bits;
        // 省略方法
    }
    
    • 发现该结构只存了一个8字节长的数据,然后通过查阅它的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 during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
    
    • 发现它提供了一个获取data()的方法,这里面应该存储了某些数据,进入返回的类型class_rw_t

    class_rw_t

    • 首先简单浏览一下该类型
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
        
        // ro_or_rw_ext 会有两种情况:
        // 1. 编译时值是 class_ro_t *
        // 2. class_rw_ext_t *,编译时的 class_ro_t * 作为 class_rw_ext_t 的 const class_ro_t *ro 成员变量保存
        explicit_atomic<uintptr_t> ro_or_rw_ext;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
    private:
        // 省略私有方法
    
    public:
        // 省略部分方法
        const method_array_t methods() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
            }
        }
        
        const property_array_t properties() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
            }
        }
    
        const protocol_array_t protocols() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
            }
        }
    };
    
    • 发现class_rw_t是一个结构体,提供了获取属性列表方法列表协议列表的方法。
    • 我们先看一下private里面的内容:
    private:
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
    
        const ro_or_rw_ext_t get_ro_or_rwe() const {
            return ro_or_rw_ext_t{ro_or_rw_ext};
        }
    
        void set_ro_or_rwe(const class_ro_t *ro) {
            ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
        }
    
        void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
            // the release barrier is so that the class_rw_ext_t::ro initialization
            // is visible to lockless readers
            rwe->ro = ro;
            ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
        }
    
        class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
    
    
    • 使用 using 关键字声明一个 ro_or_rw_ext_t 类型为: objc::PointerUnion<**const** class_ro_t, class_rw_ext_t)>;

    • 这里可以简单理解为一个指针联合体,系统只为其分配一个指针的内存空间,一次只能保存 class_ro_t 指针或者 class_rw_ext_t 指针

    • 接着我们看一下public里面的主要方法,它大致分为下面几类

      1. 获取class_rw_ext_t或者class_ro_t
      2. 获取方法列表属性列表协议列表

    ro_or_rw_ext_t

    • 这里使用using关键字申明了ro_or_rw_ext_t类型,它的具体类型是一个PointerUnion模版类
    template <class T1, class T2, typename Auth1, typename Auth2>
    class PointerUnion {
        // 仅有一个成员变量 _value
        uintptr_t _value;
    
        static_assert(alignof(T1) >= 2, "alignment requirement");
        static_assert(alignof(T2) >= 2, "alignment requirement");
    
        struct IsPT1 {
          static const uintptr_t Num = 0;
        };
        struct IsPT2 {
          static const uintptr_t Num = 1;
        };
        template <typename T> struct UNION_DOESNT_CONTAIN_TYPE {};
    
        uintptr_t getPointer() const {
            return _value & ~1;
        }
        uintptr_t getTag() const {
            return _value & 1;
        }
    
    public:
        explicit PointerUnion(const std::atomic<uintptr_t> &raw)
        : _value(raw.load(std::memory_order_relaxed))
        { }
        PointerUnion(T1 *t, const void *address) {
            _value = (uintptr_t)Auth1::sign(t, address);
        }
        PointerUnion(T2 *t, const void *address) {
            _value = (uintptr_t)Auth2::sign(t, address) | 1;
        }
    
        void storeAt(std::atomic<uintptr_t> &raw, std::memory_order order) const {
            raw.store(_value, order);
        }
    
        template <typename T>
        bool is() const {
            using Ty = typename PointerUnionTypeSelector<T1 *, T, IsPT1,
                PointerUnionTypeSelector<T2 *, T, IsPT2,
                UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
            return getTag() == Ty::Num;
        }
    
        template <typename T> T get(const void *address) const {
            ASSERT(is<T>() && "Invalid accessor called");
            using AuthT = typename PointerUnionTypeSelector<T1 *, T, Auth1,
                PointerUnionTypeSelector<T2 *, T, Auth2,
                UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
    
            return AuthT::auth((T)getPointer(), address);
        }
    
        template <typename T> T dyn_cast(const void *address) const {
          if (is<T>())
            return get<T>(address);
          return T();
        }
    };
    
    • 在定义时using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t>;对应到模版类中T1:const class_ro_t类型,T2:class_rw_ext_t类型

    成员变量

    • 只有有一个成员变量 _value
    • 这里可以理解为只能保存 const class_ro_tclass_rw_ext_t

    方法

    初始化方法
    explicit PointerUnion(const std::atomic<uintptr_t> &raw)
        : _value(raw.load(std::memory_order_relaxed))
        { }
    
    // 初始化T1
    PointerUnion(T1 *t, const void *address) {
        _value = (uintptr_t)Auth1::sign(t, address);
    }
    // 初始化T2,把_value最后一位设置为1
    PointerUnion(T2 *t, const void *address) {
        _value = (uintptr_t)Auth2::sign(t, address) | 1;
    }
    
    • 这里初始化时,不同类型,通过最后一位来区分
    存取方法
    // 根据指定的 order 以原子方式把 raw 保存到 _value 中
    void storeAt(std::atomic<uintptr_t> &raw, std::memory_order order) const {
        raw.store(_value, order);
    }
    
    
    // 获取指针 class_ro_t 或者 class_rw_ext_t 指针
    template <typename T> T get(const void *address) const {
        ASSERT(is<T>() && "Invalid accessor called");
        using AuthT = typename PointerUnionTypeSelector<T1 *, T, Auth1,
        PointerUnionTypeSelector<T2 *, T, Auth2,
        UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
    
        return AuthT::auth((T)getPointer(), address);
    }
    
    // get 函数中如果当前 _value 类型和 T 不匹配的话,强制转换会返回错误类型的指针
    // dyn_cast 则始终都返回 T 类型的指针
    template <typename T> T dyn_cast(const void *address) const {
        if (is<T>())
            return get<T>(address);
        return T();
    }
    
    类型判断
    // 定义结构体 IsPT1,内部仅有一个静态不可变 uintptr_t 类型的值为 0 的 Num。
    //(用于 _value 的类型判断, 表示此时是 class_ro_t *)
    struct IsPT1 {
        static const uintptr_t Num = 0;
    };
    
    // 定义结构体 IsPT2,内部仅有一个静态不可变 uintptr_t 类型的值为 1 的 Num。
    //(用于 _value 的类型判断,表示此时是 class_rw_ext_t *)
    struct IsPT2 {
        static const uintptr_t Num = 1;
    };
    
    // 来判断_value类型
    template <typename T>
    bool is() const {
        using Ty = typename PointerUnionTypeSelector<T1 *, T, IsPT1,
        PointerUnionTypeSelector<T2 *, T, IsPT2,
        UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
        return getTag() == Ty::Num;
    }
    
    • 通过is()方法来判断,ro_or_rw_ext 当前是 class_rw_ext_t 还是 class_ro_t

    公有方法

    获取class_rw_ext_t

    // 从 ro_or_rw_ext 中取得 class_rw_ext_t 指针
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }
    
    /*
    由 class_ro_t 构建一个 class_rw_ext_t。
        如果 ro_or_rw_ext 已经是 class_rw_ext_t 指针了,则直接返回,
        如果 ro_or_rw_ext 是 class_ro_t 指针的话,根据 class_ro_t 的值构建 class_rw_ext_t 并把它的地址赋值给 class_rw_t 的 ro_or_rw_ext,
    */
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            // 直接返回 class_rw_ext_t 指针
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            // 构建 class_rw_ext_t 
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }
    
    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    

    获取/设置class_ro_t

    // 从 ro_or_rw_ext 中取得 class_ro_t 指针,
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            // 如果此时是 class_rw_ext_t 指针,则返回它的 ro
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        
        // 如果此时是 class_ro_t,则直接返回
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }
    
    // 设置ro
    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            // 如果 ro_or_rw_ext 中保存的是 class_rw_ext_t 指针,则把 ro 赋值给 class_rw_ext_t 的 const class_ro_t *ro。
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
           // 如果 ro_or_rw_ext 中保存的是 class_ro_t *ro 的话,直接入参 ro 保存到 ro_or_rw_ext 中。
            set_ro_or_rwe(ro);
        }
    }
    

    方法、属性、协议列表

    /*
    方法列表获取
        1. class_rw_ext_t 的 method_array_t methods
        2. class_ro_t 的 method_list_t * baseMethodList 构建的 method_array_t
    */
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
    
    // 属性列表获取(同上)
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }
    
    // 协议列表获取(同上)
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
    

    class_rw_ext_t

    • 前面我们一直说class_rw_t结构中有一个ro_or_rw_ext变量,它可以是class_rw_ext_t类型指针。我们具体看一下它的结构
    struct class_rw_ext_t {
        DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
        // 指向class_ro_t的指针
        class_ro_t_authed_ptr<const class_ro_t> ro;
        // 方法列表
        method_array_t methods;
        // 属性列表
        property_array_t properties;
        // 协议列表
        protocol_array_t protocols;
        // 所属的类名
        char *demangledName;
        // 版本号
        uint32_t version;
    };
    
    • 可以看到该类中存储了方法、属性、协议列表,是可以动态扩展的列表。
    • 以及指向class_ro_t的指针

    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
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
        
        // 类名
        explicit_atomic<const char *> name;
        // 实例方法列表
        WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
        // 协议列表
        protocol_list_t * baseProtocols;
        // 成员变量列表
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        // 属性列表
        property_list_t *baseProperties;
    
        // 省略方法
    };
    
    • 它有基础方法列表、基础协议列表、成员变量列表、基础属性列表,这些内容都是编译器确定的。它是可读的,不可修改。
    • 至于分类所加载的数据、已经关联对象都存储在class_rw_ext_t

    总结

    • 看完上面内容是不是有一下疑惑?

      1. 为什么ro_or_rw_ext 会有两种类型,class_rw_ext_t或者class_ro_t类型?
      2. 为什么class_rw_ext_tclass_ro_t都有方法、属性、协议列表,那为什么还需要两个结构存储?有什么区别
    • 下面我们一一解答一下上面问题

    1. 为什么ro_or_rw_ext 会有两种类型,class_rw_ext_t或者class_ro_t类型?

    1. 如果没有在运行时,对该类的方法、属性、协议等进行动态添加,则 ro_or_rw_extclass_ro_t指针。class_ro_t是在编译期确定的。
    image
    • class_rw_t中成员变量ro_or_rw_ext直接指向class_ro_t,省去class_rw_ext_t结构体的数据,减少内存消耗。
    1. 如果在运行时,动态的添加了方法、属性、协议等,ro_or_rw_extclass_rw_ext_t指针。
    image
    • class_rw_t中成员变量ro_or_rw_ext直接指向class_rw_ext_tclass_rw_ext_t的成员变量ro指向class_ro_t

    2. 数据区别?

    • class_rw_ext_t中存储列表使用是一个继承自list_array_tt的结构,可以理解为一个二维数组,数组中存的是列表。
    • class_ro_t中存储列表使用的是一个普通数组。
    • 为什么会存储结构不同?在运行时动态添加方法时,会直接在list_array_tt类型的二维数组中,把整个分类列表插入进去。

    获取列表

    • class_rw_t最主要的作用就是存储了方法、属性、协议列表,以及提供了如何获取相关列表。

    • 下面我们主要研究一下如何获取相关列表,在开始之前,我们需要了解它们的存储方式是如何?

    • 以方法列表为例

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
    
    • 该函数返回了一个method_array_t类型结构
    class method_array_t : 
        public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
    
    • 发现该结构继承于list_array_tt结构,所以我们首先看看这个结构,它是如何存储的。

    list_array_tt

    template <typename Element, typename List, template<typename> class Ptr>
    class list_array_tt {
        struct array_t {...};
    protected:
        class iterator {...};
    private:
        union {
            Ptr<List> list;
            uintptr_t arrayAndFlag;
        };
    
        bool hasArray() const {
            return arrayAndFlag & 1;
        }
    
        array_t *array() const {
            return (array_t *)(arrayAndFlag & ~1);
        }
    
        void setArray(array_t *array) {
            arrayAndFlag = (uintptr_t)array | 1;
        }
    
        void validate() {
            for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++)
                cursor->validate();
        }
    }
    
    • 我们发现list_array_tt是一个模版类
      • typename Element:基础元数据类型(例如: method_t
      • typename List:元数据的列表类型(例如: method_list_t
    • 它内部的成员变量是一个联合体:
      • 一个单独的列表
      • 一个数组,数组中都是指针,每个指针分别指向一个列表
      • 可以理解为一维数组和二维数组的区别。
    • 除此之外,还有两个内部类

    array_t

    struct array_t {
        uint32_t count; // count 是 lists 数组中 List * 的数量
        Ptr<List> lists[0]; // 指针数组,每个元素指向一个列表
    
        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
    
    • 这里array_t是一个二维数据结构,里面含有基本的一些数据元素。

    iterator

    • 它是一个迭代器,用来遍历array_t结构中的二维数组

    entsize_list_tt

    • 还是通过上面获取方法列表的例子
    class method_array_t : 
        public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
    
    • 我们发现第二个参数传的是method_list_t类型,进入它的结构
    struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier>
    
    • 发现它继承于entsize_list_tt结构,我们看一下这个结构有何作用。
    template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
    struct entsize_list_tt {
        uint32_t entsizeAndFlags;
        // entsize_list_tt 的容量
        uint32_t count;
    
        // 元素的大小(entry 的大小)
        uint32_t entsize() const {
            return entsizeAndFlags & ~FlagMask;
        }
        uint32_t flags() const {
            return entsizeAndFlags & FlagMask;
        }
        
        // 获取对应下标元素
        Element& getOrEnd(uint32_t i) const { 
            ASSERT(i <= count);
            return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
        }
        Element& get(uint32_t i) const { 
            ASSERT(i < count);
            return getOrEnd(i);
        }
    
        size_t byteSize() const {
            return byteSize(entsize(), count);
        }
        
        static size_t byteSize(uint32_t entsize, uint32_t count) {
            return sizeof(entsize_list_tt) + count*entsize;
        }
        
        // 迭代器,用来遍历数组
        struct iterator;
        const iterator begin() const { 
            return iterator(*static_cast<const List*>(this), 0); 
        }
        iterator begin() { 
            return iterator(*static_cast<const List*>(this), 0); 
        }
        const iterator end() const { 
            return iterator(*static_cast<const List*>(this), count); 
        }
        iterator end() { 
            return iterator(*static_cast<const List*>(this), count); 
        }
        
        struct iterator {...};
    }
    
    • entsize_list_tt其实就是一个数组,用来存储编译完成后类的属性,提供了遍历和访问方法。

    方法列表

    • 用来存储该类的方法的数据结构,继承list_array_tt
    class method_array_t : 
        public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
    {
        typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
    
     public:
        method_array_t() : Super() { }
        method_array_t(method_list_t *l) : Super(l) { }
    
        const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
            return beginLists();
        }
        
        const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
    };
    
    • 前面已经说过list_array_tt结构,可能是一维数组或者二维数组,所以method_array_t也是按照如下结构存储。
    • 我们看一下单个方法的存储结构method_t
    struct method_t {
        static const uint32_t smallMethodListFlag = 0x80000000;
    
        method_t(const method_t &other) = delete;
    
        // The representation of a "big" method. This is the traditional
        // representation of three pointers storing the selector, types
        // and implementation.
        struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
    
        // 省略方法
    }
    
    • 我们发现它内部定义了一个big结构体,里面包含了方法的SELimp
    • 我们就可以通过method_t结构拿到big,就可以获取到里面的方法。

    属性列表

    • 用来存储该类的属性的数据结构,和上面类似。同样继承list_array_tt
    class property_array_t : 
        public list_array_tt<property_t, property_list_t, RawPtr>
    {
        typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
    
     public:
        property_array_t() : Super() { }
        property_array_t(property_list_t *l) : Super(l) { }
    };
    
    • 我们查看一下单个属性结构property_t
    struct property_t {
        const char *name;
        const char *attributes;
    };
    
    • 内部非常简单,一个代表变量名,一个代表变量的修饰符(strong、weak等)。

    协议列表

    • 用来存储该类的协议的数据结构,和上面类似。同样继承list_array_tt
    class protocol_array_t : 
        public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
    {
        typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super;
    
     public:
        protocol_array_t() : Super() { }
        protocol_array_t(protocol_list_t *l) : Super(l) { }
    };
    
    • 我们查看一下单个协议结构protocol_ref_t
    typedef uintptr_t protocol_ref_t;
    
    • 它就是一个指针,指向对应的协议。

    总结

    • bits中存储了属性、方法、协议、成员变量。成员变量只存储在class_ro_t中,它是编译器就决定好的,不可修改。

    • bits中这些信息,是通过class_rw_t结构存储,它有一个ro_or_rw_ext_t变量,它可以是ro类型,也可能是rw_ext类型。

      • 如果该类没有动态添加属性、方法、协议等,就是ro类型
      • 否则就是rw_ext类型,然后它内部会包含ro
      • 之所以会出现两种结构,都是为了优化内存。
    • ro是干净内存,类的加载时类本身的数据存放在ro中,编译期决定。

      • 它存储的方法、属性、协议等列表,是一个一维数组
    • rwe是脏内存,运行时动态创建成员或分类中的成员都在rwe

      • 它存储的方法、属性、协议等列表,是一个二维数据。动态添加的是个数组,编译期决定的是一个数组,方便动态添加对应信息。

    相关文章

      网友评论

        本文标题:Runtime源码解析-类中bits

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