彻底搞懂+load和+initialize
2020-07-16 本文已影响0人
康小曹
一、+load
直接过结论并且验证:
- 在main 函数执行之前调用,具体调用是在 dyld 将类加载进入 runtime 时,在 dyld_start 之后由 dyld 调用;
调用栈如图:
load方法通过dyld调用
- 分类和类分别处理,存储在两个全局表中,所有类的 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方法和macho
- 递归调用类的 +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;
二、+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 方法,其特殊之处就在于第一次调用这个类的相关方法时被调用,相当于类对象/元对象的懒加载;
总结
load 和 initialize 方法本质都是做初始化的,只不过级别或者说针对的过程不一样。load 只会调用一次,在 main 方法调用之前做初始化,比如方法交换。initialize 方法针对的是 main 方法之后,而且是懒加载,使用到时才初始化。鉴于 objc_msgSend 的机制,存在多次调用的可能,但是可以使用代码进行判断。
- 两者的调用机制完全不同,一个是使用 dyld 调用,一个是走 objc_msgSend 机制;
- load 调用先执行类的调用,再执行分类的调用,递归调用,父类的 load 方法先于子类执行。但子类和父类无关联,只有实现了 load 方法才调用,未实现也不调用父类的load 方法;
- initialize 因为是走 objc 的消息寻找和转发机制,所以分类会覆盖原类的 initialize 方法。initialize 在类第一次被使用到时调用,虽然也是循环调用,但是子类未实现时需要调用父类的 initialize 方法来完成类的初始化操作;