+load和+initialize

+load方法

当一个类或者分类被加载到Objectie-C的Runtime运行环境中时,会调用它对应的+load方法。对于所有静态库中和动态库中实现了+load方法的类和分类都有效。

当应用启动时,首先要fork进程,然后进行动态链接。+load方法的调用就是在动态链接这个阶段进行的。动态链接结束之后,会执行程序的main函数。

dyld简介

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。整个加载过程可细分为九步:

  • 1、设置运行环境
  • 2、记载共享缓存
  • 3、实例化主程序
  • 4、加载插入的动态库
  • 5、链接主程序
  • 6、链接插入的动态库
  • 7、执行弱符号绑定
  • 8、执行初始化方法
  • 9、查找入口点并返回

步骤8,执行初始化方法。如果看过dyld源码或者源码分析的,可以知道这个步骤是在initializeMainExecutable函数中完成的。dyld会有限初始化动态库,然后初始化主程序。该函数经过系列的执行会进入notifySingle方法,随后会调用到load_images方法,然后会调用到call_load_methods方法。

调用栈

所以,+load方法会在dyld阶段的执行初始化方法中执行。
多说一点,dyld的初始化顺序:

  • 调用所有Framework中的初始化方法
  • 调用所有的+load方法
  • 调用C++ 的静态初始化方法及C/C++ 中的attribute(constructor)函数
  • 调用给所有链接到目标文件的framework中的初始化方法
+load方法执行顺序
类与类之间的+load方法的执行顺序

有继承关系的类的+load方法的执行顺序,是从基类到子类的;没有继承关系的两个类的+load方法的执行顺序是与编译顺序有关的(Build Phases -> Compile Sources中的顺序)。

类与分类之间+load方法的执行顺序

所有分类的+load方法都在所有类+load方法之后执行,同时又发现所有分类的+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
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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方法

  • 调用call_category_loads方法
  • 重复1->2,直到所有的类和分类的+load方法都执行完毕
  • 所以在这里也能看出来,所有的类的+load方法都执行在分类的+load方法之前。

call_category_loads方法基本上与load_class_loads方法类似,同时还做了一些其他操作。在这里看,我们也就能了解,该函数会获取到所有类及分类的+load方法并执行,所以我们不必手动调用[super load]方法,也能执行到父类的+load方法。

多个镜像中存在+load方法的执行顺序

动态库由于与主工程不是同一个镜像,所以他们之间的输出是分开的,而且动态库的链接要优先于主工程的链接,来保证主工程链接时能链接到期望的动态库。所以动态库的+load方法都要在主工程的+load方法之前执行。其中动态库中类与子类、类与类之间的+load方法的执行顺序,与之前说的一致,这里就不再赘述。

静态库中的类的+load方法,是必须要有代码调用才能加载链接,并且其类的+load方法的执行顺序与编译顺序有关(Link Binary With Libraries的顺序)。

静态库中的分类的+load方法没有调用,其实经常使用静态库开发的同学就知道了,要在主工程的other linker flag中设置-all_load。

如果在+load方法中调用[super load]

我们知道分类如果与类方法重名了,那么在之后调用时,会调用分类的同名方法,如果多个分类都实现了这个方法,那么就会按照编译顺序,最后执行最后编译的分类中的同名方法,执行到分类的+load方法时,会把该方法再次执行一次。
所以为了避免一些不必要的麻烦,我们就不必手动去写[super load]方法,同时也不要自己手动调用[object load]方法。

结合了例子以及dyld、Runtime的源码,弄清楚了+load方法的执行时机,以及顺序。下面就是一些总结

  • 1、+load方法是在dyld阶段的执行初始化方法步骤中执行的,其调用为load_images -> call_load_methods
  • 2、一个类在代码中不主动调用+load方法的情况下,其类、子类实现的+load方法都会分别执行一次
  • 3、父类的+load方法执行在前,子类的+load方法在后
  • 4、在同一镜像中,所有类的+load方法执行在前,所有分类的+load方法执行在后
  • 5、同一镜像中,没有关系的两个类的执行顺序与编译顺序有关(Compile ources中的顺序)
  • 6、同一镜像中所有的分类的+load方法的执行顺序与编译顺序有关(Compile Sources中的顺序),与是谁的分类,同一个类有几个分类无关
  • 7、同一镜像中主工程的+load方法执行在前,静态库的+load方法执行在后。有多个静态库时,静态库之间的执行顺序与编译顺序有关(Link Binary With Libraries中的顺序)
  • 8、不同镜像中,动态库的+load方法执行在前,主工程的+load执行在后,多个动态库的+load方法的执行顺序编译顺序有关(Link Binary With Libraries中的顺序)。
  • 9、当多个分类有相同的方法时,调用顺序为后编译的先调用。




+initialize方法

一个类或者它的子类收到第一条消息(手写代码调用,+load方法不算)之前调用,可以做一些初始化的工作。但该类的+initialize的方法调用,在其父类之后。
Runtime运行时以线程安全的方式将+initialize消息发送给类。也就是说,当一个类首次要执行手动调用的代码之前,会等待+initialize方法执行完毕后,再调用该方法。

这里需要注意的一点:

当子类没有实现+initialize或者子类在+initialize中显式的调用了[super initialize],那么父类的+initialize方法会被调用多次。如果希望避免某一个类中的+initialize方法被调用过多次,可以使用下面的方法来实现:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

因为+initialize是以阻塞方式调用的,所以很重要的一点就是将方法实现限制为可能最小的工作量。

本文主要通过官方文档、例子以及Runtime源码,分析了+initialize方法的调用,总结如下:

  • 1、当代码执行到一个类第一次调用方法时,会调用这个类的+initialize方法
  • 2、在调用自身类的+initialize方法之前,会判断其父类链上是否有类还没有执行+initialize方法,如果没有执行,那么执行。所以所有父类的+initialize方法都执行在前,子类的+initialize执行在后。
  • 3、如果一个类有多个分类都实现了+initialize方法,那么会执行编译顺序的最后一个分类实现的+initialize方法
  • 4、当一个类实现了+initialize方法,但是子类没有实现+initialize或者子类在实现+initialize方法中显式的调用的[super initialize]方法,那么该类的+initialize方法会调用多次,如果不想该方法被多次调用,可以在该类的+initialize方法通过if (self = [ClassName self])进行判断来避免多次调用。




+load方法与+initialize方法的区别

调用方式

+load方法: 根据函数地址直接调用
+initialize方法: 是通过objc_msgSend调用

调用时机

+load方法:是Runtime加载类、分类的时候调用(如果不显式调用,只会调用一次)
+initialize方法:是类第一次接收到消息的时候调用(如果不显式调用,可能存在调用多次的风险)

调用顺序
+load方法
  • 先调用类的+load方法,再调用分类的+load方法
  • 有继承关系的类,先调用父类的+load,后调用子类的+load方法
  • 没有继承关系的类,会按照编译顺序来执行+load方法
  • 所有的分类,都按照编译顺序来执行+load方法
+initialize方法
  • 先调用父类的+initialize方法,后调用子类的+initialize方法
  • 如果一个类有分类,那么会调用最后编译的分类实现的+initialize方法
  • 通过消息机制调用,当子类没有实现+initialize方法时,会调用父类的initialize方法

总结:

  • load方法一个类只会调用一次(除去手动调用),而调用的数序是,从superclass -> class -> category,category里面的顺序是先编译,先调用
  • initialize方法,一个类可能会调用多次,如果子类没有实现initialize方法,当第一次使用此类的时候,会调用superclass。而调用的顺序是,superclass -> 实现initialize的category 或者 实现了initialize方法(没有category实现initialize) 或者 superclass的initialize (没有子类和category实现initialize方法)


参考:
+load方法的执行顺序你了解么?
+initialize方法的调用时机

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

推荐阅读更多精彩内容