一、load 方法理论
+load
方法会在 runtime 加载类、分类时调用(在main函数之前)。每个类、分类的 +load
在程序运行过程中都会被调用一次。调用顺序是:先调用类的+load
方法,再调用分类的 +load
。其中分类的 +load
方法按照编译先后顺序调用(即先编译,先调用),类的 +load
方法是先调用父类,再调用子类的+load
。
上述描述有个反常的地方,和笔者之前分析的这篇文章结论相反。原因主要在于:+load
方法是根据方法地址直接调用,并不是经过 objc_msgSend
函数调用。具体可以看第二小节 +load
方法底层分析。
二、load 方法底层分析
分析 +load
底层源码,可以参考如下顺序阅读源码:
objc-os.mm ---> load_images :
--->prepare_load_methods :
--->schedule_class_load --->add_class_to_loadable_list、add_category_to_loadable_list
--->call_load_methods:
--->call_class_loads--->(*load_method)(cls, SEL_load)
--->call_category_loads--->(*load_method)(cls, SEL_load)
2.1 先调用类再调用分类 load
call_load_methods
方法中可以清晰看出 do while
循环中,先调用call_class_loads
方法,再遍历调用call_category_loads
方法(即先调用所有类的load方法,再遍历调用所有分类方法)。
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
//先调用所有类的 load 方法
while (loadable_classes_used > 0) {
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
进一步call_class_loads
方法内部通过遍历调用各个类的load
方法(主要借助(*load_method)(cls, SEL_load)
函数),call_category_loads
内部实现同理。
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 = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
//遍历调用所有类的 load 方法,并依次调用
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
2.2 先调用父类再调用子类 load
prepare_load_methods
方法中调用schedule_class_load
,schedule_class_load
方法调用add_class_to_loadable_list
。schedule_class_load
方法是递归调用,先是传入父类,再传入子类,内部的add_class_to_loadable_list
会将父类放在数组前面,然后层层返回将子类添加到数组后面。最终数组中前面保存的是父类,后面是子类。最终,2.1 中的call_class_loads
函数内部从前往后遍历数组,调用对应类的 +load
方法, 所以父类 +load
方法调用顺序优先于子类。
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
三、initialize 方法理论
+initialize
方法会在类第一次接收到消息时调用,是通过objc_msgSend
进行调用的。调用顺序为:
- 1、如果分类实现了
+initialize
,就覆盖类本身的+initialize
调用, 即只调用分类的+initialize
,不调用类的+initialize
方法。 - 2、子类第一次接受消息时,先调用父类的
+initialize
(如果父类之前已经调用过+initialize
方法就不再调用),再调用子类的+initialize
。
第一点和常规方法调用规则一致;第二点可以结合 runtime 源码进行分析,这一点和常规方法调用不同。
四、initialize 方法底层分析
分析 +load
底层源码,可以参考如下顺序阅读源码:
objc-msg-arm64.s:
--->objc_msgSend
objc-runtime-new.mm:
class_getInstanceMethod--->lookUpImpOrNil--->lookUpImpOrForward--->
_class_initialize--->callInitialize--->objc_msgSend(cls, SEL_initialize)
顺着上述源码顺序查找,一直到 _class_initialize
函数中,该函数中存在递归调用情况。先是_class_initialize
函数传入父类,通过callInitialize
函数调用父类的 +initialize
方法 ,最后再一层层返回调用子类的 +initialize
方法。
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
//这里是重点
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
//递归
_class_initialize(supercls);
}
......
......
#if __OBJC2__
@try
#endif
{
//该函数中调用 objc_msgSend 方法,最终调用 Initialize 方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
......
......
}
五、小结
+initialize
和 +load
区别在于如下:
调用方式:
load 根据函数地址调用。
initialize 通过objc_msgSend
进行调用。
调用时刻:
+load方法会在runtime加载类、分类时调用。
+initialize方法会在类第一次接收到消息时调用。
分类和本类调用顺序:
先调用类的 load,再调用分类的 load(和常规函数调用不同)。
如果调用了分类的 initialize,类的 initialize 不再被调用(同常规函数调用)。
类继承调用顺序:
先调用父类的 load ,再调用子类的load(源码中的递归逻辑)
先调用父类的 initialize,后调用子类的 initialize(源码中的递归逻辑)注: 如果父类之前已经调用过+initialize方法就不再调用父类的 initialize
** 调用优先级总结**
- load: 父类load -> 子类load -> 分类load
- initialize:分类initialize -> 父类initialize -> 子类initialize 注:按照此顺序,只要调用过一次,后续不再调用