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_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(源码中的递归逻辑)注: 如果父类之前已经调用过+initialize方法就不再调用父类的 initialize

** 调用优先级总结**

  • load: 父类load -> 子类load -> 分类load
  • initialize:分类initialize -> 父类initialize -> 子类initialize 注:按照此顺序,只要调用过一次,后续不再调用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容