在iOS中,所有的类都继承自NSObject,在这个类中,有两个类方法,分别是 +load 与 +initialize,他们承担着不同的任务,今天详细的解读一下它们各自的意义以及调用原理。
+load
load方法会先调用所有父类+load,然后调用子类+load,最后调用分类 +load,分类的调用顺序由 Compile Source决定。
从 runtime的源码来解开+load方法调用的原理。
首先**void prepare_load_methods(header_info *hi) **函数:
void prepare_load_methods(header_info *hi)
{
size_t count, i;
rwlock_assert_writing(&runtimeLock);
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
函数主要作用是准备好被调用的类和分类,具体实现方法在 schedule_class_load(remapClass(classlist[i]));函数中。
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);
}
在 schedule_class_load(cls->superclass);中可以看出,每次都传入父类作为参数递归的调用,决定了方法的调用顺序,父类有限,待方法调用完成,会在全局变量 loadable_classes 和 loadable_categories 中。
当类准备好,接下来调用+load方法,void call_load_methods(void) 函数
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;
void *pool = objc_autoreleasePoolPush();
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
其中,主类的调用顺序优于分类的调用,具体调用方法在 call_class_loads(); 和 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.
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_internal(classes);
}
在这个方法中可以看到,使用方法首地址进行直接调用,并非使用objc_msgSend,通过这个方式,我们可以在分类中做一些事情,例如 Method Swizzling
+initialize
+initialize 方法会在类收到第一个消息时调用,是一个懒加载的方式,如果一直没收到消息,则永不调用。这种设计节省了资源。
在 objc-runtime-new.mm中我们可以看到。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
rwlock_unlock_write(&runtimeLock);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
...
}
当向类发送消息时,如果类没有初始化,则会调用初始化方法 void _class_initialize(Class cls)
void _class_initialize(Class cls)
{
...
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);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
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]",
cls->nameForLogging());
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
...
}
这里我们可以看出,其实+initialize本质为objc_msgSend,走的是方法调用的流程,如子类没有实现+initialize,则会调用父类+initialize方法,如分类实现了+initialize方法,则会覆盖主类方法(这里的覆盖不是真正的覆盖,主类的方法还存在方法列表中,只是runtime命中对应方法后不会继续向下搜索,则直接调用该方法)。
我们可以避免父类+initialize方法多次执行
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
总结:
+load | +initialize | |
---|---|---|
调用时机 | 添加到runtime时 | 第一次发送消息 |
调用顺序 | 父类->子类->分类 | 父类->子类(可被分类覆盖) |
调用次数 | 1 | N |
是否需要显式调用父类实现 | NO | NO |
是否沿用父类的实现 | NO | YES |
分类中的实现 | 都执行 | 如覆盖,不执行主类 |
参考链接:
http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/
https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html
http://blog.iderzheng.com/objective-c-load-vs-initialize/
http://nshipster.com/method-swizzling/