• Runtime——探索类,对象,分类本质


    Runtime是什么?

    Runtime顾名思义为运行时,他其实是一套底层纯C语言的API,OC的代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言的基础,他使OC具备了面向对象性和动态性。

    所谓动态性是基于c/c++这样的静态语言区别的,在c/c++中我们编译成什么就会执行什么,且不实现定义时会报错,而OC则不会,我们甚至可以使用方法调换技术,改变我们编译时执行的函数A,令其执行函数B。

    那么我们之前写的OC代码是怎么和Runtime交互的,大概分为三种层次,OC源代码,NSObject方法,Runtime函数,交互程度是由高到低的。
    下面探索一下OC对象的实质,他底层是怎么样的c语言代码。

    类和对象本质

    这里的话需要下载一下runtime源码——Runtime
    我们找到源码中关于类和对象的定义可以看到。
    在这里插入图片描述
    类和对象的本质都是结构体。

    对象本质

    我们点进去对象这个结构体。

    struct objc_object {
    private:
        
        // 私有成员变量: isa指针
        isa_t isa;
        // 自定义的一些成员变量
    public:
    // 一些共有方法.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以看到这块一个系统自带的变量isa指针,点进去发现他是个共用体。

    // MARK: - isa的声明
    // 这里分析一下共用体
    /* 共用体和结构体其实都可以定义很多变量,区别就是共用体占用的内存为变量最宽的内存。
        共同体中所有的成员变量共同使用这一块内存,很节省空间,但容易覆盖
    */
    union isa_t {
        /// isa_t的构造函数
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits; // 八个字节 64位
    #if defined(ISA_BITFIELD)
        struct {
            // isa 结构的定义
            /* 其实所有的数据都存储在成员变量bits里面,这个结构体利用位域
             来增加代码可读性,让我们看看bits对应的位上分别存储什么。
             */
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;          /* isa是否经过优化,如果指为0,代表无优化,就是一个isa指针,
            64位全部用来存储对象所属类的地址,如果为1,则采取当前位分配。*/                             \
          uintptr_t has_assoc         : 1;      /*判断有无关联对象,如果没有快速销毁对象。 */                                 \
          uintptr_t has_cxx_dtor      : 1;      /*判断当前对象有无析构函数,更快销毁 */                                 \
          uintptr_t shiftcls          : 33; /*当前对象所属类的地址信息 */ \
          uintptr_t magic             : 6;     /*标记调试时,当前对象是否初始化 */                                  \
          uintptr_t weakly_referenced : 1;  /*标记弱引用表里是否有当前对象弱指针数组,也就是是否被若指针指向,以及是否有弱引用*/                                    \
          uintptr_t deallocating      : 1;         /*当前对象是否释放。 */                              \
          uintptr_t has_sidetable_rc  : 1;  /*引用计数表里面是否有当前对象的引用记数*/                                      \
          uintptr_t extra_rc          : 19/*对象的引用计数 - 1,存不下了就回放到引用计数表中*/
    #   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
    
    // SUPPORT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    可见对象的本质是一个objc_object结构体,该结构体的内部有一个固定的成员变量isa,在64位操作系统以后对isa做了优化,它不再是一个指针,而是一个isa_t的共用体,其占用8个字节64位,在不同架构(arm64,x86)下各位段长度不一样,在arm64中,33位被用来存储对象所属类的地址信息,19位存储对象的引用计数-1这一操作,存不下的放到引用计数表里(存值范围为0-255),1位表示是否有若引用,其他位也有各种标记信息。

    类的本质

    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,内部存储方法,属性,遵循的协议等
    }
    // MARK: - class_rw_t结构声明
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint32_t version;
    
        // class_ro_t成员变量 ,只读性
        const class_ro_t *ro;
    
        method_array_t methods;//存储着该类的所有实例方法,包括分类
        property_array_t properties;//存储着该类的所有属性,包括分类
        protocol_array_t protocols;// 存储着该类所有遵守的协议信息,包括分类
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    }
    // MARK: - class_ro_t结构声明
    // 该类的只读信息ro-readonly
    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;// 存储该类本身的属性信息
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    可以看出类的本质还是一个 objc_class类型结构体,只不过其内部结构套了两层rwro
    class_ro_t:只读信息,内部存储着经过编译后一个类本身定义的所有实例方法,属性,协议,成员变量。
    class_rw_t是在运行时系统生成的,把ro里本身定义的信息复制到rw里,并把分类相关的信息合并到rw中,rw是可读可写的,这就解释了分类为什么不能给类增加成员变量。

    元类本质

    所谓元类,是指一个类所属的类,我们每创建一个类,系统就会自动帮我们创建好该类所属的类——即元类。因此在OC里对象其实分为实例对象、类对象、元类对象三类,我们开发中经常说的“对象”其实是指狭义的对象一实例对象,知道了这一点就好理解了,实例对象有它所属的类-即一个类对象,类对象也有它所属的类–即一个元类对象,元类对象也有它所属的类–即基类的元类对象。

    其实元类和类的本质都是objc_class结构体,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法信息,而元类的methods成员变量里存储着该类所有的类方法信息。

    所有元类都使用根元类作为他们的类,根元类的isa指针指向了它自己。

    三者的关系。
    使用最常见的这张图进行分析 instance(实例) Subclass(子类)

    每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类)。
    在这里插入图片描述

    分类的本质

    分类是OC的一个高级特性,我们一般用它给系统的类或者第三方库的类扩展方法,属性和协议,或者把一个类不同功能分散到不同的模块中实现。
    在这里插入图片描述
    在runtime中查看一下分类的源码。

    // MARK: - category 完整声明结构
    struct category_t {
        // 这里是没有类拓展的成员变量列表
        const char *name; // 该分类所属类的名字
        classref_t cls;   // 指向该分类所属的类
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;// 拓展的类方法
        struct protocol_list_t *protocols; // 拓展的属性列表
        struct property_list_t *instanceProperties;// 拓展的实例方法。
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;// 类拓展的协议
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    就是一个结构体,然后可以看到里面是没有成员变量列表存在的,这样可以解释分类为什么不能添加成员变量。

    分类的实现原理

    通过上面我们可以分析得到,我们知道一个类的实例方法存储在类里面,所有的类方法在元类里面,而对象调用方法isa指针先到相应的类的和元类,然后在其方法列表中查找,那么分类是这么实现这一功能的。

    因为isa指针指向了分类的所属类,所以肯定不是这一套,正确的应该是编译器在运行时利用Runtime动态把分类的数据合并到类和元类,分类的数据在类本身数据之前,越晚编译的分类越在前面,所以如果分类里面有和类里面同名的方法,就会优先调用分类里的方法,如果多个分类有同名的方法,会优先调用后编译方法里面的方法,我们可以去Compile里控制分类编译顺序。

    那么Runtime具体是怎么实现的?
    运行时,系统读取镜像阶段,会读取所有的类,如果发现分类,遍历所有的分类,根据分类的cls指针找到它所属的类,重新组织一下这个类内部的结构——即合并分类的数据。
    以下是runtime.new中的read_images方法,这个方法是处理镜像,这里取出了关于分类的部分。

    // Discover categories. 
        for (EACH_HEADER) {
            // 读取当前类所有的分类
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
            // 倒序取出每一个分类
            // 越晚编译的分类的实例方法列表反而在最前面
            for (i = 0; i < count; i++) {
                // 读取某一个分类
                category_t *cat = catlist[i];
                // 根据这个分类的cls指针找个这个分类所属的类
                Class cls = remapClass(cat->cls);
                // 处理cls指针缺失的特殊情况
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
            // 开始合并分类的数据
                // 先将实例方法,协议,属性添加到基类里面
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    // 取出当前分类(没有合并过的),合并实例方法,以及协议。
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        // 合并到所属类的方法列表里面
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
                // 然后将拓展的类方法,类协议,类属性添加到基类里面
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    // 获取未合并的分类的信息,这里ISA指的所属类的元类
                    // 与上文提到的类方法存储在元类中符合
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        // 合并到元类中
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", 
                                     cls->nameForLogging(), cat->name);
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    这里有两个关键的函数。
    1.addUnattachedCategoryForClass,这个函数就是对比当前分类是否被合并

    static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                              header_info *catHeader)
    {
        runtimeLock.assertLocked();
    
        // DO NOT use cat->cls! cls may be cat->cls->isa instead
        NXMapTable *cats = unattachedCategories();
        category_list *list;
    
        list = (category_list *)NXMapGet(cats, cls);
        if (!list) {
            list = (category_list *)
                calloc(sizeof(*list) + sizeof(list->list[0]), 1);
        } else {
            list = (category_list *)
                realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
        }
        list->list[list->count++] = (locstamped_category_t){cat, catHeader};
        NXMapInsert(cats, cls, list);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.remethodizeClass,这个方法就是合并数据的方法。

    // 将未处理的分类数据添加到现在的类,修复所属类的方法列表,协议列表,属性列表。
    // 更新类和其子类的方法缓存
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertLocked();
    
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    
    /将方法列表、属性和协议从类别附加到类。//假设cat的分类都是加载并按加载顺序排序的,//最老的分类优先。
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
        // 开辟二维数组存放每个分类里面的某个列表。
        // fixme rearrange to remove these intermediate allocations
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        // 可以看到这里时倒序遍历所有分类。
        while (i--) {
            // 获取一个分类
            auto& entry = cats->list[i];
            // 获取分类的类方法,存入二维数组
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
            // 属性列表存入二维数组
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
            // 协议列表存入二维数组
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
        // 将这个二维数组内所有一维数组的收地址存入rw类的methods成员变量指向的地址
        auto rw = cls->data();
    
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        // 给当前类的实例方法列表附加所有分类的实例方法列表
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
        // 存入属性
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
        // 存入协议
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    
    // 添加方法列表具体实现
        // addedLists 所有分类的方法列表,就是二维数组
        // addedCount 分类个数
        void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return;
    
            if (hasArray()) {
                // many lists -> many lists
                // 获取原来的methods元素个数(元素为数组)
                uint32_t oldCount = array()->count;
                // 计算新的
                uint32_t newCount = oldCount + addedCount;
                // 重新为methods成员变量指向的数组分配内存,一个指针八个字节。
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
                array()->count = newCount;
                // 分配完内存后对内村数据进行移动和复制
                // 先将旧的数据移动到新开辟的空间的最后面,可以内存覆盖
                // 其实就是原来列表的首地址放在后面
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                // 再将新列表的新数据复制到新开辟的空间最前面,没有内存覆盖
                // 新列表的首地址复制到前面
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
            } 
            else {
                // 1 list -> many lists
                List* oldList = list;
                uint32_t oldCount = oldList ? 1 : 0;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)malloc(array_t::byteSize(newCount)));
                array()->count = newCount;
                if (oldList) array()->lists[addedCount] = oldList;
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    这样就完成了将所有分类的数据合并到类的操作。最终类的方法列表,分类的在前面,而原来的方法列表在后面。

  • 相关阅读:
    vue或webpack加载highcharts与highcharts-3d
    【web-4】Nginx
    2022年都说软件测试不香了?在职3年月薪16k我满意了,你们觉得前景怎么样?
    Fedora Linux 38下Mariadb数据库设置utf8mb4字符编码
    uni-app 系统状态栏高度CSS变量--status-bar-height
    Pinia简单上手
    centos安装神通数据库
    322. 零钱兑换
    Linux系统使用AndroidStudio创建桌面快捷键
    学成在线第四天
  • 原文地址:https://blog.csdn.net/chabuduoxs/article/details/125366193