+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方法)