来源:饶志臻(@iOS_饶志臻)
链接:http://www.jianshu.com/p/9daec08ec370
点击 → 了解如何加入专栏作者
先了解一下应用启动之后,做了什么。
main.m 中的 main() 是程序的入口,但在进入 main 函数之前,程序就执行了很多代码(不然也不会启动那么久)。
启动后执行顺序:
将程序依赖的动态链接库加载进内存
加载可执行文件中的所有符号、代码
runtime 解析被编译过的符号代码,遍历所有 Class,按继承层级依次调用Class 的 load 方法和其 Category 的 load 方法。
首先来做点测试,来看看 load 方法的执行顺序。
先建一个 Single View Application。展开 Build Phases 的 Compile Sources,如下图:

在每个类的 @implementation 里加上
+ (void)load {
NSLog(@"%s", __func__);
}
来看看每个类的 load 方法的调用顺序(main.m 我做了特殊处理)。以下是运行结果:
JMTestDemo[14939:1769791] +[ViewController load]
JMTestDemo[14939:1769791] +[AppDelegate load]
JMTestDemo[14939:1769791] +[ClassMain load]
顺序和 Compile Sources 顺序一致,目前来看,Compile Sources 的顺序就是 load 方法的调用顺序。
再来一个测试,依次添加 ClassFather,以及它的子类 ClssSon,以及另一个 ClassA。结果 Compile Sources 的顺序和我想象中的不一样,如下图

看起来毫无规律啊。(这是一个问题,先记录一下)
这个时候我改变顺序,将 ClassSon 移动到最前面,ClassFather 移动到最后面。如图

下面是运行结果:
JMTestDemo[15034:1788736] +[ClassFather load]
JMTestDemo[15034:1788736] +[ClassSon load]
JMTestDemo[15034:1788736] +[ViewController load]
JMTestDemo[15034:1788736] +[AppDelegate load]
JMTestDemo[15034:1788736] +[ClassA load]
JMTestDemo[15034:1788736] +[ClassMain load]
也就是本应先执行 ClassSon load 方法,但先执行了 ClassFather load 方法,再来一次测试。
我将 ClassSon 里的 load 方法注释掉,以下是运行结果:
JMTestDemo[15055:1791953] +[ViewController load]
JMTestDemo[15055:1791953] +[AppDelegate load]
JMTestDemo[15055:1791953] +[ClassA load]
JMTestDemo[15055:1791953] +[ClassMain load]
JMTestDemo[15055:1791953] +[ClassFather load]
也就是说,只有在你重写了 load 方法的时候,会在执行你的 load 之前,当父类未加载时,先执行父类的 load 方法。
再来一个分类的情况,分类就很有意思了。先添加了 ClssSon+category,ClssSon+category2,ClassFather+category。将它们移动至最上方。如图
运行结果
JMTestDemo[15181:1808284] +[ClassFather load]
JMTestDemo[15181:1808284] +[ClassSon load]
JMTestDemo[15181:1808284] +[ViewController load]
JMTestDemo[15181:1808284] +[AppDelegate load]
JMTestDemo[15181:1808284] +[ClassA load]
JMTestDemo[15181:1808284] +[ClassMain load]
JMTestDemo[15181:1808284] +[ClassSon(category2) load]
JMTestDemo[15181:1808284] +[ClassFather(category) load]
JMTestDemo[15181:1808284] +[ClassSon(category) load]
明明应该最早执行 load 方法的分类,却统统最后执行,甚至晚于和它没有啥关系的 ClassMain load。所以分类低人一等,最晚执行 load 方法。
得出结论,load 的执行顺序满足以下几条
这个时候来测试 initialize 的执行顺序,在 ClassFather ClassSon 以及他们的分类都重写 initialize 方法,并将 ClassFather 移动至 ClassSon 的前面,在 ClassFather load 里添加调用 ClassSon 的类方法的代码,如下
+ (void)load {
NSLog(@"%s", __func__);
[ClassSon method];
}
运行结果如下
JMTestDemo[15325:1836741] +[ClassFather load]
JMTestDemo[15325:1836741] +[ClassFather(category) initialize]
JMTestDemo[15325:1836741] +[ClassSon(category) initialize]
JMTestDemo[15325:1836741] +[ClassSon method]
JMTestDemo[15325:1836741] +[ClassSon load]
JMTestDemo[15325:1836741] +[ViewController load]
JMTestDemo[15325:1836741] +[AppDelegate load]
JMTestDemo[15325:1836741] +[ClassA load]
JMTestDemo[15325:1836741] +[ClassMain load]
JMTestDemo[15325:1836741] +[ClassSon(category2) load]
JMTestDemo[15325:1836741] +[ClassSon(category) load]
JMTestDemo[15325:1836741] +[ClassFather(category) load]
从运行结果来看,先执行了 ClassFather(category) initialize,再执行了 ClassSon(category) initialize,而 ClassSon load 在后面执行。
也就是说 load 方法还未执行也不会影响到这个类的使用。
另一个现象是执行子类 initialize 的时候会先执行其父类的 initialize。且 category 的覆写效应对 load 方法无效,但对 initialize 方法有效。且按 Complile Sources 的顺序,ClassSon(category2) 先覆写了 ClassSon 的 initialize 方法,接着 ClassSon(category) 覆写了 ClassSon(category2) 的 initialize。
如果将子类以及类别的 initialize 注释掉,再修改 ClassFather(category) initialize ,如下
+ (void)initialize {
NSLog(@"调用者:%@ 调用方法:%s",NSStringFromClass(self), __func__);
}
结果如下
JMTestDemo[15458:1863222] +[ClassFather load]
JMTestDemo[15458:1863222] 调用者:ClassFather 调用方法:+[ClassFather(category) initialize]
JMTestDemo[15458:1863222] 调用者:ClassSon 调用方法:+[ClassFather(category) initialize]
JMTestDemo[15458:1863222] +[ClassSon method]
也就是子类会继承父类的 initialize 。当执行完父类的 initialize 方法,准备执行子类的 initialize 方法时,会根据继承链找到父类的 initialize 执行。为了防止重复执行 initialize 里的代码,可以根据调用者来决定是否执行 initialize 里的其它代码。
这块写了一部分,但查资料的时候查到写的非常不错的,我觉得我没有写的必要了,留下链接,强烈建议想对 iOS 开发中的类和对象有更深了解的人看看。
从 NSObject 的初始化了解 isa
深入解析 ObjC 中方法的结构
iOS程序main函数之前发生了什么