iOS 类的加载原理中

作者: 晨曦的简书 | 来源:发表于2021-07-28 14:04 被阅读0次

    类的加载原理:
    iOS 类的加载原理上
    iOS 类的加载原理中
    iOS 类的加载原理下
    分类的加载原理补充及类扩展 , 关联对象介绍

    iOS 类的加载原理上 中我们讲到 readClass 方法,该方法通过类的地址对类的名称与地址进行绑定匹配。但是我们还不了解类的具体实现过程,这里我们来继续探究一下。

    realizeClass 分析


    我们在 _read_images 函数中找到跟类处理相关的代码,并在这两处地方加上测试代码并打上断点,运行之后发现代码只执行了 3781 行的打印(这里执行的前提是实例的 load 方法要有调用),紧接着执行了 realizeClassWithoutSwift 函数。那么我们就来分析一下 realizeClassWithoutSwift 函数做了哪些事情。

    realizeClassWithoutSwift


    首先我们判断 clsLGPerson 的时候在 auto ro = (const class_ro_t *)cls->data() 这一行打上断点,来打印 baseMethodList 的信息。正常的情况下这里应该会打印出 LGPerson 类的方法列表,但是这里并没有打印出来。说明在这里方法还没有被加载进来。所以我们继续往下看方法的加载是什么时候实现的。
    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        auto ro = (const class_ro_t *)cls->data();
        auto isMeta = ro->flags & RO_META;
        // 判断是否是元类
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro();
            ASSERT(!isMeta);
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // 不是元类的话会走到这里,这里主要是把 ro 的数据复制到 rw
            // Normal class. Allocate writeable class data.
            rw = objc::zalloc<class_rw_t>();
            rw->set_ro(ro);
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            cls->setData(rw);
        }
    
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    #if SUPPORT_NONPOINTER_ISA
        if (isMeta) {
            // 如果是元类的话就设置为 non pointer ISA
            cls->setInstancesRequireRawIsa();
        } else {
            bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
            bool rawIsaIsInherited = false;
            static bool hackedDispatch = false;
    
            // 如果做了环境变量相关的配置,这里不管是不是元类,instancesRequireRawIsa 都会赋值为 true
            if (DisableNonpointerIsa) {
                instancesRequireRawIsa = true;
            }
    
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        /**
         supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
         metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
         上面这两行加上这里两行就构成了类的继承链关系及 isa 走位图
         */
        // 这里会设置父类及元类
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
        // Connect this class to its superclass's subclass lists
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        methodizeClass(cls, previously);
        return cls;
    }
    

    auto ro = (const class_ro_t *)cls->data() 之后主要做了以上注释中的这些事情,那么当执行到 methodizeClass(cls, previously) 这行的时候方法是否被加载进来了呢?我们在 methodizeClass 方法打断点看一下。


    这里可以看到 baseMethodList 依然没有数据,说明在这里方法依然没被加载进来。那么我们继续来分析在 methodizeClass 方法中做了哪些事情。

    methodizeClass 分析

    我们继续往下执行,在执行到 prepareMethodLists 的时候可以看到 list 在这里是有值的,只是打印不出来。

    prepareMethodLists

    紧接着我们来到 prepareMethodLists 方法,在这里会执行到 fixupMethodList 方法,而且打印的 list 地址跟 method_list_t *list = ro->baseMethods(); 这里的地址是一样的。接着进入到修复方法。

    fixupMethodList

    static void 
    fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
    {
        runtimeLock.assertLocked();
        ASSERT(!mlist->isFixedUp());
    
        if (!mlist->isUniqued()) {
            mutex_locker_t lock(selLock);
        
            // Unique selectors in list.
            for (auto& meth : *mlist) {
                // sel_cname 方法会获取到 SEL
                const char *name = sel_cname(meth.name());
                
                printf("排序前 : %s - %p",name,meth.name());
                
                // 这里主要是把 sel 中的名字跟地址设置到 meth,这里之后就可以打印成功了
                meth.setName(sel_registerNameNoLock(name, bundleCopy));
            }
        }
    
        // 这里会判断是否是 isSmallList,如果时候 isSmallList 就不用重新进行排序,不是 isSmallList 的话就会根据地址对方法列表进行排序
        if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
            method_t::SortBySELAddress sorter;
            std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
        }
        
        for (auto& meth : *mlist) {
            // sel_cname 方法会获取到 SEL
            const char *name = sel_cname(meth.name());
            printf("排序后 : %s - %p",name,meth.name());
        }
    
        
        // Mark method list as uniqued and sorted.
        // Can't mark small lists, since they're immutable.
        if (!mlist->isSmallList()) {
            mlist->setFixedUp();
        }
    }
    

    fixupMethodList 方法中会遍历 mlist,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist 进行重新排序。

    最后我们通过打印验证可以看到,方法列表确实重新排序了。


    在执行完 fixupMethodList 方法后我们再次打印 baseMethodList 还是没有数据,所以我们接着往下探究。

    懒加载类与非懒加载类

    非懒加载类

    非懒加载类:当前类实现了 load 方法,map_images 的时候加载。

    上面我们也讲过,执行 realizeClassWithoutSwift 方法的前提是当前类要实现 load 方法,这时候就是非懒加载类。然后就会执行 read_iamges -> read_class(名字 - 类) -> realizeClassWithoutSwift(ro - rw superclass isa) -> methodizeClass() -> prepareMethodLists(写入方法名 + 方法列表排序) 这个流程。但是这样的缺点就是比较耗时跟消耗内存,那么如果懒加载会执行哪些流程呢?这里我们注释 load 方法来看一下。

    懒加载类

    懒加载类:当前类没有实现 load 方法,当前类接收的第一条消息的时候才会加载,节约内存,提高加载速度。


    当我们注释 load 方法运行的时候会来到这里执行 realizeClassWithoutSwift 方法。我们在这里加上测试代码,并打上断点 bt 输出函数调用堆栈信息。

    这里可以看出懒加载类的加载流程 lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass

    而且在 main 函数这里可以看到懒加载类的加载执行是在 LGPerson 第一次调用方法的时候。

    分类的本质

    #import <Foundation/Foundation.h>
    #import "LGPerson.h"
    #import <objc/runtime.h>
    
    extern void _objc_autoreleasePoolPrint(void);
    
    @interface LGPerson (LG)
    
    @property (nonatomic, copy, nullable) NSString *cate_name;
    
    @property (nonatomic, assign) int cate_age;
    
    - (void)cate_instanceMethod1;
    - (void)cate_instanceMethod2;
    + (void)cate_classMethod3;
    
    @end
    
    @implementation LGPerson (LG)
    
    - (void)cate_instanceMethod1 {
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod2 {
        NSLog(@"%s",__func__);
    }
    + (void)cate_classMethod3 {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            LGPerson *p = [LGPerson alloc];
            [p say1];
            
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    __attribute__((constructor)) void kcFunc(void){
        printf("来了 : %s \n",__func__);
    }
    

    我们在 main.mLGPerson 类添加一个分类 LGPerson (LG)cdmain.m 所在文件目录,然后输入 clang -rewrite-objc main.m -o main.cpp 命令,最后会看到生成了一个 main.cpp 文件。




    我们打开 main.cpp 文件,在最后可以看到 LGPerson (LG) 的底层代码。接着我们点进到 _category_t 查看分类的结构。我们可以看到结构体里面有 name,这里 name 的名称就是 LGcls 这里就是就是 LGPerson 类,而且可以看到 instance_methodsclass_methods,这里相对于类的结构多了 class_methods,这是因为分类没有元类,接着就是 protocols(协议)properties(关联属性)

    这里我们也可以看到对应我们添加的类方法跟对象方法,但是这里没有 set 方法跟 get 方法,也验证了分类添加属性是通过关联对象进行处理的。这里是生成的 cpp 文件我们看到的,那么在源码中分类的结构是否跟这里一致呢?


    这里可以看到结构跟我们在 cpp 文件中看到的基本一直,只是多了一个 _classProperties,但是类属性不是一直都有。

    相关文章

      网友评论

        本文标题:iOS 类的加载原理中

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