load 和 initialize 方法的执行顺序以及类和对象的关系


来源:饶志臻(@iOS_饶志臻)   

链接:http://www.jianshu.com/p/9daec08ec370

点击 → 了解如何加入专栏作者


先了解一下应用启动之后,做了什么。
main.m 中的 main() 是程序的入口,但在进入 main 函数之前,程序就执行了很多代码(不然也不会启动那么久)。
启动后执行顺序:
将程序依赖的动态链接库加载进内存
加载可执行文件中的所有符号、代码
runtime 解析被编译过的符号代码,遍历所有 Class,按继承层级依次调用Class 的 load 方法和其 Category 的 load 方法。

load 方法的执行顺序

首先来做点测试,来看看 load 方法的执行顺序。
先建一个 Single View Application。展开 Build Phases 的 Compile Sources,如下图:


load 和 initialize 方法的执行顺序以及类和对象的关系_第1张图片
屏幕快照 2016-05-10 13.02.51.png

在每个类的 @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 的顺序和我想象中的不一样,如下图


load 和 initialize 方法的执行顺序以及类和对象的关系_第2张图片
屏幕快照 2016-05-10 13.10.18.png

看起来毫无规律啊。(这是一个问题,先记录一下)

这个时候我改变顺序,将 ClassSon 移动到最前面,ClassFather 移动到最后面。如图


load 和 initialize 方法的执行顺序以及类和对象的关系_第3张图片
屏幕快照 2016-05-10 13.49.31.png

下面是运行结果:

    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。将它们移动至最上方。如图

load 和 initialize 方法的执行顺序以及类和对象的关系_第4张图片
屏幕快照 2016-05-10 14.10.56.png

运行结果

    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 的执行顺序满足以下几条

  • 执行子类的 load 之前,当父类未加载时,先执行父类的 load 方法。
  • 分类的 load 方法统一在最后执行
  • 优先满足以上两条,再满足按 Compile Sources 的顺序执行 load 方法。

initialize 方法的执行顺序

这个时候来测试 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函数之前发生了什么



文/饶志臻(简书作者)
原文链接:http://www.jianshu.com/p/9daec08ec370
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

你可能感兴趣的:(Objective-C)