源码

load 和 initialize 方法底层本质

一、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_load,schedule_class_load 方法调用add_class_to_loadable_list。schedule_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(源码中的递归逻辑)

作者:ZhengYaWei

链接:https://www.jianshu.com/p/b433d337c0f2

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/load-he-initialize-fangfadicengbenzhi.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:11 月 27, 2018 at 02:09 下午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复