+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和+initialize_第1张图片
调用栈

所以,+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中的顺序)。




+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方法的执行顺序你了解么?
+initialize方法的调用时机

你可能感兴趣的:(+load和+initialize)