可能
+load
方法应该是每个iOS开发同学都非常熟悉的方法,而且面试中+load
方法相关的面试提也是非常常见,但你了解的+load
方法真的跟实际上的一样么?
看文章之前先思考几个问题
+load
方法在什么时候?+load
方法是如何执行的?- 一个类的
+load
方法会执行几次? - 类和分类的
+load
方法的执行顺序? - 同一个类的不同分类的
+load
方法的执行顺序? - 父类和子类的
+load
方法的执行顺序? - 没有继承关系的两个类的
+load
方法的执行顺序? - 静态库、动态库中的
+load
方法与主程序的+load
的执行顺序? - 为什么我们在
+load
方法中不必写[super load]
如果有些问题不确定或者不知道,那么就往下看,我们一起来解决这些问题。
+load
方法的执行时机
官方文档是这样描述的:
Invoked whenever a class or category is added to the Objective-C runtime。The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.
当一个类或者分类被加载到Objectie-C的Runtime运行环境中时,会调用它对应的+load
方法。对于所有静态库中和动态库中实现了+load
方法的类和分类都有效。
当应用启动时,首先要fork进程,然后进行动态链接。+load
方法的调用就是在动态链接这个阶段进行的。动态链接结束之后,会执行程序的main函数。
dyld简介
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。整个加载过程可细分为九步:
- 设置运行环境
- 记载共享缓存
- 实例化主程序
- 加载插入的动态库
- 链接主程序
- 链接插入的动态库
- 执行弱符号绑定
- 执行初始化方法
- 查找入口点并返回
各个步骤具体做的事情,请参考该博客。
步骤8,执行初始化方法。如果看过dyld源码或者源码分析的,可以知道这个步骤是在initializeMainExecutable
函数中完成的。dyld会有限初始化动态库,然后初始化主程序。该函数经过系列的执行会进入notifySingle
方法,随后会调用到load_images
方法,然后会调用到call_load_methods
方法。我们之前分析过dyld,如果感兴趣请看之前发表的这篇博客。如果不想研究源码也没关系,我们随便写一个工程,新建一个类并实现+load
方法,打断点定位,我们也能得到下图的调用栈。
所以到这里,我们得到了第一个问题的答案:+load
方法会在dyld阶段的执行初始化方法
中执行。
多说一点,dyld的初始化顺序:
- 调用所有Framework中的初始化方法
- 调用所有的
+load
方法 - 调用C++ 的静态初始化方法及C/C++ 中的
attribute(constructor)
函数 - 调用给所有链接到目标文件的framework中的初始化方法
+load
方法的执行顺序
官方文档中提到了+load
方法的执行顺序
- 一个类的
+load
方法调用在它的父类的+load
方法之后 - 一个分类的
+load
方法调用在它本身类的+load
方法之后
类与类之间的+load
方法的执行顺序
我们写一个demo来验证一下。新建一个iOS工程,然后新建一个Person
类
@interface Person : NSObject
@end
@implementation Person
+ (void)load
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
然后新建一个Student
类继承Person
类。
@interface Student : Person
@end
@implementation Student
+ (void)load
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
再新建一个HighSchoolStudent
类继承Student
。
@interface HighSchoolStudent : Student
@end
@implementation HighSchoolStudent
+ (void)load
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
到这里,我们看到了一条继承链。运行程序,得到结果
---- 0x10b04c0c0 +[Person load]
---- 0x10b04c160 +[Student load]
---- 0x10b04c1b0 +[HighSchoolStudent load]
结果如预期,接下来,我们增加一个Animal
类
@interface Animal : NSObject
@end
@implementation Animal
+ (void)load
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
现在看一下结果
---- 0x1094930e8 +[Person load]
---- 0x109493138 +[Animal load]
---- 0x109493188 +[Student load]
---- 0x1094931d8 +[HighSchoolStudent load]
我们发现,Animal
类的+load
方法也调用了,但是它的调用顺序,我们还不知道是如何的。这个时