load 和 initialize 方法底层本质

作者: ZhengYaWei | 来源:发表于2018-11-26 23:26 被阅读174次

    一、load 方法理论

    +load 方法会在 runtime 加载类、分类时调用(在main函数之前)。每个类、分类的 +load 在程序运行过程中都会被调用一次。调用顺序是:先调用类的+load方法,再调用分类的 +load。其中分类的 +load 方法按照编译先后顺序调用(即先编译,先调用),类的 +load 方法是先调用父类,再调用子类的+load

    上述描述有个反常的地方,和笔者之前分析的这篇文章结论相反。原因主要在于:+load 方法是根据方法地址直接调用,并不是经过 objc_msgSend 函数调用。具体可以看第二小节 +load 方法底层分析

    二、load 方法底层分析

    分析 +load底层源码,可以参考如下顺序阅读源码:

    objc-os.mm ---> load_images :
          --->prepare_load_methods :
                  --->schedule_class_load --->add_class_to_loadable_list、add_category_to_loadable_list     
          --->call_load_methods: 
                  --->call_class_loads--->(*load_method)(cls, SEL_load)
                  --->call_category_loads--->(*load_method)(cls, SEL_load)
    

    2.1 先调用类再调用分类 load

    call_load_methods方法中可以清晰看出 do while 循环中,先调用call_class_loads 方法,再遍历调用call_category_loads方法(即先调用所有类的load方法,再遍历调用所有分类方法)。

    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
           //先调用所有类的 load 方法
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            //再调用所有分类的 load 方法
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    进一步call_class_loads方法内部通过遍历调用各个类的load 方法(主要借助(*load_method)(cls, SEL_load)函数),call_category_loads 内部实现同理。

    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list.
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // Call all +loads for the detached list.
        //遍历调用所有类的 load 方法,并依次调用
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            (*load_method)(cls, SEL_load);
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    

    2.2 先调用父类再调用子类 load

    prepare_load_methods 方法中调用schedule_class_loadschedule_class_load 方法调用add_class_to_loadable_listschedule_class_load方法是递归调用,先是传入父类,再传入子类,内部的add_class_to_loadable_list会将父类放在数组前面,然后层层返回将子类添加到数组后面。最终数组中前面保存的是父类,后面是子类。最终,2.1 中的call_class_loads函数内部从前往后遍历数组,调用对应类的 +load 方法, 所以父类 +load 方法调用顺序优先于子类。

    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        assert(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // Ensure superclass-first ordering
        schedule_class_load(cls->superclass);
    
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    void add_class_to_loadable_list(Class cls)
    {
        IMP method;
    
        loadMethodLock.assertLocked();
    
        method = cls->getLoadMethod();
        if (!method) return;  // Don't bother if cls has no +load method
        
        if (PrintLoading) {
            _objc_inform("LOAD: class '%s' scheduled for +load", 
                         cls->nameForLogging());
        }
        
        if (loadable_classes_used == loadable_classes_allocated) {
            loadable_classes_allocated = loadable_classes_allocated*2 + 16;
            loadable_classes = (struct loadable_class *)
                realloc(loadable_classes,
                                  loadable_classes_allocated *
                                  sizeof(struct loadable_class));
        }
        
        loadable_classes[loadable_classes_used].cls = cls;
        loadable_classes[loadable_classes_used].method = method;
        loadable_classes_used++;
    }
    

    三、initialize 方法理论

    +initialize 方法会在类第一次接收到消息时调用,是通过objc_msgSend 进行调用的。调用顺序为:

    • 1、如果分类实现了+initialize,就覆盖类本身的 +initialize 调用, 即只调用分类的 +initialize,不调用类的 +initialize 方法。
    • 2、子类第一次接受消息时,先调用父类+initialize(如果父类之前已经调用过+initialize方法就不再调用),再调用子类+initialize

    第一点和常规方法调用规则一致;第二点可以结合 runtime 源码进行分析,这一点和常规方法调用不同。

    四、initialize 方法底层分析

    分析 +load底层源码,可以参考如下顺序阅读源码:

    objc-msg-arm64.s:
        --->objc_msgSend
    
    objc-runtime-new.mm:
        class_getInstanceMethod--->lookUpImpOrNil--->lookUpImpOrForward--->
        _class_initialize--->callInitialize--->objc_msgSend(cls, SEL_initialize)
    

    顺着上述源码顺序查找,一直到 _class_initialize函数中,该函数中存在递归调用情况。先是_class_initialize函数传入父类,通过callInitialize函数调用父类的 +initialize 方法 ,最后再一层层返回调用子类的 +initialize 方法。

    void _class_initialize(Class cls)
    {
        assert(!cls->isMetaClass());
    
        Class supercls;
        bool reallyInitialize = NO;
    
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
    //这里是重点
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            //递归
            _class_initialize(supercls);
        }
    ......
    ......
    #if __OBJC2__
            @try
    #endif
            {
              //该函数中调用 objc_msgSend 方法,最终调用 Initialize 方法
                callInitialize(cls);
    
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
    ......
    ......
    }
    

    五、小结

    +initialize+load 区别在于如下:

    调用方式:
    load 根据函数地址调用。
    initialize 通过objc_msgSend 进行调用。

    调用时刻:
    +load方法会在runtime加载类、分类时调用。
    +initialize方法会在类第一次接收到消息时调用。

    分类和本类调用顺序:
    先调用类的 load,再调用分类的 load(和常规函数调用不同)。
    如果调用了分类的 initialize,类的 initialize 不再被调用(同常规函数调用)。

    类继承调用顺序:
    先调用父类的 load ,再调用子类的load(源码中的递归逻辑)
    先调用父类的 initialize,在调用子类的 initialize(源码中的递归逻辑)

    相关文章

      网友评论

      • SunnyFengzi:大兄弟 第一段文字 :”类的 +load 方法是先调用子类,再调用父类的+load“ 不对吧,应该是先调用父类的load
      • 梁森的简书:load方法不会被覆盖、initialize方法会被覆盖。 load方法在runtime中一定被执行,initialize方法不一定被执行。
        SunshineBrother:1、initialize被覆盖,但是我们可以使用dispatch_once
        2、initialize方法在我们对类初始化的时候就会调用,如果我们runtime修改系统方法,没有初始化的过程,那么我们可以使用load,如果有执行初始化的就可以使用initialize。
        3、load方法在我们一运行的时候就会全部执行,initialize方法是初始化类的时候才会执行,为了让我们app启动的更快,我们最好不要把全部方法都放在load中执行

      本文标题:load 和 initialize 方法底层本质

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