[toc]
参考
http://www.jianshu.com/p/a358a397a4ce
http://blog.csdn.net/ShengQiangLiu/article/details/50866228
code
// NSObject 有实现该方法
- (void)load;
objc4源码解读
// objc-os.mm
_objc_init();
// objc-runtime-new.mm
load_images();
// 准备(查找所有被实现的load方法)
prepare_load_methods(); // Discover load methods
/// 类 ★
// 按(编译)顺序加载 classlist 数组中的类, 这个顺序就是 BuildPhases 中 CompileSources 的顺序
classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count);
schedule_class_load(remapClass(classlist[i])); // for循环
// ★★ 递归调用, 传入super, 所以父类早于子类添加到数组中, 保证父类早于子类load
schedule_class_load(cls->superclass);
// ★★ 将cls添加到 loadable_classes 数组的最后面, call_class_loads()是从前往后遍历, 所以后添加的后加载;
add_class_to_loadable_list();
method = cls->getLoadMethod(); // 获取 load 的IMP
if (!method) return; // ★★ 如果类没有实现 +load, 则不加入数组, 后面也不会去调用
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
/// 分类 ★
// 按(编译)顺序加载 categorylist 数组中的分类
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
// for循环, 将 cat 添加到 loadable_categories
// 注意, 分类这里没有递归调用, 不用管 super
add_category_to_loadable_list();
method = _category_getLoadMethod(cat);
if (!method) return;
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
// 加载
call_load_methods(); // objc-loadmethod.mm
// 先调用所有类的load
call_class_loads();
// 从上面prepare好的 loadable_classes 数组中取出所有类
struct loadable_class *classes = loadable_classes;
// for循环该数组, 取到类的load方法的内存地址, 赋值给函数指针load_method
load_method_t load_method = (load_method_t)classes[i].method;
// 使用函数指针, 直接调用每一个类的load方法
(*load_method)(cls, @selector(load));
// 再调用所有分类的load
call_category_loads();
load_method_t load_method = (load_method_t)cats[i].method;
(*load_method)(cls, @selector(load));
结论★★:
-
系统调用
+load
方法是根据方法地址直接调用, 并不是经过objc_msgSend
函数调用。- 所以, 所有类的已实现的load 都会被调用 (未实现则不会调用, 也不会去调用父类的)。
- 主类的 load 方法, 并不会被分类覆盖。
-
先调用类的
+load
- 各个类之间, 按照编译先后顺序调用 (先编译,先调用)。
- 调用子类的
+ load
之前会先调用父类的+ load
-
再调用分类的
+ load
(分类的 load 是在所有主类 load 完毕之后才调用)。- 各个分类之间, 按照编译先后顺序调用 (先编译,先调用)。
调用时机:
+ load
方法会在runtime加载类、分类时调用 [TBC ??? load 是 runtime 调用的吗]
程序一启动, 在main()
函数执行之前, 当类或分类被加载到内存时被调用。
换句话说, 这个load方法在 didFinishLaunchingWithOptions 之前就被调用了;
《Apple Document》
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
调用次数:
每个类、分类的+ load
,在程序运行过程中, 默认会且只会执行一次
调用必然性:
必然调用, 不管程序运行过程中有没有用到这个类, 都会调用load方法 (如果有实现)
调用顺序:
同一继承体系下, 先加载父类, 再加载子类, 然后再加载子类的分类(按编译顺序, 先编译先调用);
不同的类之间的加载顺序: 是不确定的 按照编译先后顺序调用(先编译, 先调用)。
《Apple Document》
A class’s +load method is called after all of its superclasses’ +load methods.
A category +load method is called after the class’s own +load method.
见【objc4源码解读 - 结论】
父类
父类先于子类加载, 子类不要手动调用 [super load]
, 否则父类的load
会执行多次。
load 不遵循继承规则, 不管子类有没有写load方法, 都不会去查找调用父类的load
分类
与其他方法不同, 每个类的load都是独立的, 不存在继承、重写, 在Category中重写load函数不会替换原始类中的load, 原始类和Category中的load函数都会被执行, 原始类的load会先被执行, 再执行Category中的load函数。
当有多个 Category 都实现了load函数, 这几个load函数都会执行, 按编译顺序, 先编译先调用。
调用方式:
系统自动调用, 不要手动调用 (但实际也能调用)
安全性
线程安全 内部加锁 线程阻塞
在load方法中使用其他类是不安全的, 因为会调用其他类的load方法, 而如果关系复杂的话, 就无法判断出各个类的载入顺序, 类只有初始化完成后, 类实例才能进行正常使用
尽可能的精简load方法, 因为整个应用程序在执行load方法时会阻塞, 即, 程序会阻塞直到所有类的load方法执行完毕, 才会继续
应用场景
一般的应用场景是在该方法中实现方法交换(Method Swizzling)
面试题
Category中有load方法吗?load方法是什么时候调用的?
有load方法
load方法在runtime加载类、分类的时候调用 【TBC】
load 方法能继承吗?★★
-
答案
load 方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用。
-
验证:
先创建继承自 NSObject 的 QGPerson, 实现 +load 方法, 添加打印代码;
然后创建继承自 QGPerson 的 QGStudent, 不实现 +load 方法;
然后在
main()
函数中, 手动调用[QGStudent load];
会发现
QGPerson
的+load
在main()
前后被调用了2次。可见 +load 是存在继承的, 如果自己没有实现, 可以沿着super_class调用父类的。
-
解析:
首先,
[QGStudent load];
这样写就是消息发送机制, 相当于objc_msgSend([QGStudent class], @selector(load));
会根据 QGStudent 的 isa 找到其元类对象, 在其元类对象中查找 load, 找不到, 再根据 super_class 找到父元类对象, 从而找到并调用了父类的 +load。
load、initialize的区别?
-
调用方式
load 是根据函数地址直接调用
initialize 是通过 objc_msgSend 调用
-
调用时刻
load 是 runtime 加载类、分类的时候调用(只会调用1次)
initialize 是类第一次接收到消息的时候调用, 每一个类只会 initialize 一次(父类的initialize方法可能会被调用多次)
load、initialize 的调用顺序?
-
load
【见本文- 源码 - 结论】
-
先初始化父类
再初始化子类(可能最终调用的是父类的initialize方法)
load、initialize 在category中的调用的顺序?
load、initialize 出现继承时他们之间的调用过程?
系统是怎么调用 load 方法的?
不是通过消息机制, 而是直接通过函数指针调用