一、+load
结论一:
-
+load
方法在main
函数执行之前调用;
调用栈如图:
具体推到和验证详见:iOS类加载流程(一):类加载流程的触发
结论二:
- 分类和类分别处理,存储在两个全局表中,所有类的
+load
方法调用完毕之后再调用分类的+load
方法;
源码如下:
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes NOBSS = NULL;
// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories NOBSS = NULL;
__private_extern__ void prepare_load_methods(header_info *hi)
{
size_t count, i;
rwlock_assert_writing(&runtimeLock);
class_t **classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
class_t *cls = remapClass(classlist[i]);
// 内部会调用add_class_to_loadable_list添加到loadable_classes数组中
schedule_class_load(cls);
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
// Do NOT use cat->cls! It may have been remapped.
class_t *cls = remapClass(cat->cls);
realizeClass(cls);
assert(isRealized(cls->isa));
// 这个方法中会存入 loadable_categories
add_category_to_loadable_list((Category)cat);
}
}
将类添加到数组的代码如下,分类的就不列出了:
static void schedule_class_load(class_t *cls)
{
assert(isRealized(cls)); // _read_images should realize
if (cls->data->flags & RW_LOADED) return;
class_t *supercls = getSuperclass(cls);
if (supercls) schedule_class_load(supercls);
add_class_to_loadable_list((Class)cls);
changeInfo(cls, RW_LOADED, 0);
}
dyld 会分别将类和分类添加到两个全局数组中,以后循环取出这两个数组中的元素,递归执行 +load
。正因为这两个数组独立,这也就是分类的 +load
不覆盖原来类的 +load
方法的本质;
结论三:
- 在(类先于分类调用)、(父类先于子类)的大前提下,分类中
+load
方法的调用顺序取决于符号表中的顺序,也就是编译顺序;
如下:
结论四:
- 递归调用类的
+load
方法,因此父类会先于子类调用,分类最后调用;
结论五:
- dyld 中并没有使用
objc_msgSend
来调用函数,而是直接执行,所以不存在方法查找和消息传递机制,所以子类未实现+load
方法的情况下不会去调用父类的+load
;
源码如下:
__private_extern__ void call_load_methods(void)
{
static BOOL loading = NO;
BOOL more_categories;
recursive_mutex_assert_locked(&loadMethodLock);
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
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);
loading = NO;
}
这里也可以看出,两个 while 循环,先通过 call_class_loads
执行所有类的 load 方法,再通过 call_category_loads
执行分类的 load 方法。
其中,执行类的 load 方法代买如下:
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = NULL;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
IMP load_method = classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
}
(*load_method) ((id) cls, SEL_load);
}
// Destroy the detached list.
if (classes) _free_internal(classes);
}
从上面可以看到,直接取出类地址之后执行,并没有调用 objc_msgSend
。因此,如果子类没有实现 +load
方法,此时并不会去调用父类的 +load
;
结论六:实现了 +load
方法的类会被添加到 mach-O 的 __DATA,__objc_nlclslist
表中,调用 +load
方法之前,会通过该表取出类的静态数据,完成对类的初始化;
注意,这个表 MachOView 不会解析,不代表该表中没有内容。内容如下:
因为表中的数据是 8 字节,所以很容易猜到存储的是指针。另外,因为 __PAGEZERO
段的存在,所以先调整下 MachOView 中的显示方式:
iOS 中是大小端,所以上述三个地址应该是:
0x0100009358
0x01000093A8
0x01000093F8
对应的地址中的数据如下:
而这三个类就是实现了 +load
方法的三个类;
另外,从前面的代码中也可以看出来,先通过 _getObjc2NonlazyClassList
和 _getObjc2NonlazyCategoryList
取出了类的静态数据和分类数据,再通过 realizeClassWithoutSwift
完成了类的初始化,最后才通过 call_load_methods
进行 +load
方法的调用;
这里调用
realizeClassWithoutSwift
只是一个兜底逻辑,其实非懒加载类在 map 时,已经被 objc 完成了初始化。load_image
主要的逻辑是通过非懒加载类的列表来遍历调用+load
方法;
二、+initalize
先看一眼源码:
__private_extern__ void _class_initialize(Class cls)
{
Class supercls;
BOOL reallyInitialize = NO;
// Get the real class from the metaclass. The superclass chain
// hangs off the real class only.
cls = _class_getNonMetaClass(cls);
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = _class_getSuperclass(cls);
if (supercls && !_class_isInitialized(supercls)) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
_class_setInitializing(cls);
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: calling +[%s initialize]",
_class_getName(cls));
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
_class_getName(cls));
}
// Done initializing.
......
}
代码中有几个关键点:
-
- 初始化自己之前,递归执行父类的初始化操作;
supercls = _class_getSuperclass(cls);
if (supercls && !_class_isInitialized(supercls)) {
_class_initialize(supercls);
}
这里很明显是先判断父类有没有初始化完成,如果没有则递归执行父类的初始化;
-
- 初始化方法是通过 objc_msgSend 调用的,需要经过方法查找和消息转发的过程;
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
因此,可以做出几个结论:
-
+initialize
方法是在这个类第一次被使用到时才调用,具体为第一次调用该类的相关方法; - 父类的
+initialize
先执行; - 如果子类没有实现
+initialize
,则会调用父类的+initialize
; - 如果子类实现了
+initialize
,那么就直接执行子类的+initialize
; - 理论上只会调用一次,但是因为采用了
objc_msgSend
来调用,所以如果子类没有实现+initialize
,那么就会多次调用父类的+initialize
,可以通过添加if (self == [ClassName self])
来进行判断; - 不像
+load
方法,会区分类和分类保存在两个数组中分别执行, 分类的+initialize
方法会覆盖原来类的+initialize
,且遵循分类的编译顺序原则,最靠后的分类最终替换掉之前的+initialize
方法;
说白了,initialize 就是一个普通 OC 方法,其特殊之处就在于第一次调用这个类的相关方法时被调用,相当于类对象/元对象的懒加载;
三、+initialize多次调用
关于 +initialize
被多次调用的场景:
- 子类未实现
+ initialize
,父类的+initialize
会被多次调用;
当第一次使用父类时,父类的 + initialize
被调用,当第一次使用子类时,因为子类无该方法,通过 objcMsgSend
传递机制,调用到父类的 + initialize
,所以此时会被多次调用,因此, Apple 建议的做法是:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
而 [Class self] 的源码如下:
+ (id)self {
return (id)self;
}
该方法直接返回方法的调用者,也就是类对象。所以以上代码就是在排除掉子类调用 +initialize
方法的情况,确保只被初始化一次;
源码中的 _class_isInitialized
只是确保在调用父类 +initialize
方法之前,父类已经初始化完成了,而不是说父类初始化完成就不调用了;
四、总结
+load
和 +initialize
方法本质都是做初始化的,只不过级别或者说针对的过程不一样。
非懒加载类在动态链接时已经被加载,而懒加载类在运行时被使用到时才加载。加载的过程就是对类的方法、属性、协议等进行装配,以完成类和元类的初始化操作。懒加载类如果实现了 +load
方法,那么该类就被添加到了非懒加载表,在 objc 的 map_image
流程中已经初始化完成了。只不过在后续的 load_image
流程中才触发 +load
方法的调用。这些都发生 dyld 流程中,在 main
函数之前。
+initialize
方法针对的是 main
方法之后,而且是懒加载,使用到时才初始化。鉴于 objc_msgSend 的机制,存在多次调用的可能,但是可以使用代码进行判断。
总结:
- 目的不同:
+load
方法用于添加类在加载完成后的一些逻辑,initialize
方法用于在类第一次被使用到时进行一些初始化操作; - 时机不同:一个处于 dyld 流程中,位于
main
函数之前,一个处于 runloop 开启之后,位于main
函数之后; - 机制不同:
+load
方法的调用是根据类继承关系进行符号调用,所以子类如果未实现则不会调用父类的+load
方法。而initialize
是通过消息转发机制调用,子类未实现则进入父类的方法查找逻辑最终调用,所以需要添加一些判断来阻断该逻辑。