load
该方法会在 runtime 加载类和分类时调用,可以通过自定义的实现来进行类的一些定制化操作。一般用来执行一些只会执行一次的代码。
从官方文档可以看出 load 的调用顺序:
- 先调用父类的 +load,再调用子类的。
- 先调用类的 +load,再调用分类的。
- 如果是不是继承关系的类,则是先编译先调用;同级分类的也是先编译先调用。
下面通过源码来证实一下上面的调用关系。源码版本为 objc4-818.2。
因为 +load 会在 runtime 加载类和分类时调用,所以源码入口还是 _objc_init 方法。在该方法中会调用 load_images 方法。
- load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
......
// Discover load methods,先找出所有的 load 方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant),再去加载 load 方法。
call_load_methods();
}
- prepare_load_methods:查找所有的类和分类的 load 方法。
void prepare_load_methods(const headerType *mhdr)
{
......
// 查找类的 load 方法
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 查找分类的 load 方法
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
......
add_category_to_loadable_list(cat);
}
}
- schedule_class_load:查找所有类的 load 方法。
static void schedule_class_load(Class cls)
{
......
// Ensure superclass-first ordering,通过这个递归可以验证:先加载父类,再加载子类这一结论。
schedule_class_load(cls->getSuperclass());
add_class_to_loadable_list(cls);
......
}
- call_load_methods
void call_load_methods(void)
{
......
// 通过这个do-while 循环可以验证:先调用类的 load 方法,再调用分类的 load 方法这一结论。
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// 调用类的 load
call_class_loads();
}
// 2. Call category +loads ONCE,调用分类的 load
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
......
}
- call_class_loads:调用类的 load 方法。
static void call_class_loads(void)
{
......
// 通过方法地址直接调用
(*load_method)(cls, @selector(load));
......
}
- call_category_loads:调用分类的 load 方法。
static bool call_category_loads(void)
{
......
// 通过方法地址直接调用
(*load_method)(cls, @selector(load));
......
}
(*load_method)(cls, @selector(load)); 从这句代码我们也可以得出一个结论:+load 方法直接通过方法地址调用,而不是走的消息机制那套流程。
runtime 的 +load 调用流程图:
initialize
通过官方文档可以得出下面几个结论:
- 它在类第一次接受消息时调用且每个类只会调用一次。
- 它是线程安全的。
- 如果子类没有实现该方法,那父类可能会多次调用;如果分类实现了该方法,则只会调用分类的方法实现。
// 以下代码可以避免调用多次
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
接着,还是通过源码来验证下上面的结论。
- class_initialize
Class class_initialize(Class cls, id obj)
{
runtimeLock.lock();
return initializeAndMaybeRelock(cls, obj, runtimeLock, false);
}
- initializeAndMaybeRelock
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
......
// runtimeLock is now unlocked, for +initialize dispatch
ASSERT(nonmeta->isRealized());
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
- initializeNonMetaClass
void initializeNonMetaClass(Class cls)
{
......
// Make sure super is done initializing BEFORE beginning to initialize cls.从下面的递归可以验证:+initialize 先调用父类再调用子类
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
......
callInitialize(cls);
......
}
- callInitialize
void callInitialize(Class cls)
{
// objc_msgSend,从此可以验证:+initialize 是通过消息机制调用。
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
+initialize 调用流程图:
总结
- 调用时机:+load 在 Runtime 加载类和分类时调用; +initialize 是类在第一次接受消息时调用;
- 调用本质:+load 是通过方法地址直接调用;+initialize 则是通过消息机制调用。
- 调用顺序:
- +load 先调用类,再调用分类。若类有父类则先调用父类再调用子类。
- +load 平级类则先编译先调用;平级分类也是如此。
- +initialize 先调用父类再调用子类。若分类也实现则
只会调用分类。 - 若子类没有实现 +initialize ,则父类可能会调用多次。