一、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 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(源码中的递归逻辑)
作者:ZhengYaWei
链接:https://www.jianshu.com/p/b433d337c0f2