+load
+load 方法是當(dāng)類或分類被添加到 Objective-C runtime 時(shí)被調(diào)用的,實(shí)現(xiàn)這個(gè)方法可以讓我們?cè)陬惣虞d的時(shí)候執(zhí)行一些類相關(guān)的行為。子類的 +load 方法會(huì)在它的所有父類的 +load 方法之后執(zhí)行,而分類的 +load 方法會(huì)在它的主類的 +load 方法之后執(zhí)行。但是不同的類之間的 +load 方法的調(diào)用順序是不確定的,根據(jù)編譯順序來(lái)決定。
+initialize
+initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用。也就是說(shuō) +initialize 方法是以懶加載的方式被調(diào)用的,如果程序一直沒(méi)有給某個(gè)類或它的子類發(fā)送消息,那么這個(gè)類的 +initialize 方法是永遠(yuǎn)不會(huì)被調(diào)用的。那這樣設(shè)計(jì)有什么好處呢?好處是顯而易見(jiàn)的,那就是節(jié)省系統(tǒng)資源,避免浪費(fèi)。
總結(jié)
通過(guò)閱讀 runtime 的源碼,我們知道了 +load 和 +initialize 方法實(shí)現(xiàn)的細(xì)節(jié),明白了它們的調(diào)用機(jī)制和各自的特點(diǎn)。下面我們繪制一張表格,以更加直觀的方式來(lái)鞏固我們對(duì)它們的理解:
+load
調(diào)用時(shí)機(jī) 被添加到 runtime 時(shí)
調(diào)用順序 父類->子類->分類
調(diào)用次數(shù) 1次
是否需要顯式調(diào)用父類實(shí)現(xiàn) 否
是否沿用父類的實(shí)現(xiàn) 否
分類中的實(shí)現(xiàn) 類和分類都執(zhí)行
+initialize
調(diào)用時(shí)機(jī) 收到第一條消息前,可能永遠(yuǎn)不調(diào)用
調(diào)用順序 父類->子類
調(diào)用次數(shù) 多次
是否需要顯式調(diào)用父類實(shí)現(xiàn) 否
是否沿用父類的實(shí)現(xiàn) 是
分類中的實(shí)現(xiàn) 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn)
原理
“源碼面前沒(méi)有秘密”。最后,我們來(lái)看看蘋(píng)果開(kāi)放出來(lái)的部分源碼。從中我們也許能明白為什么load和initialize及調(diào)用會(huì)有如上的一些特點(diǎn)。
其中l(wèi)oad是在objc庫(kù)中一個(gè)load_images函數(shù)中調(diào)用的,先把二進(jìn)制映像文件中的頭信息取出,再解析和讀出各個(gè)模塊中的類定義信息,把實(shí)現(xiàn)了load方法的類和Category記錄下來(lái),最后統(tǒng)一執(zhí)行調(diào)用。
其中的prepare_load_methods函數(shù)實(shí)現(xiàn)如下:
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);
}
}
這大概就是主類中的load方法先于category的原因。再看下面這段:
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);
}
這正是父類load方法優(yōu)先于子類調(diào)用的原因。
再來(lái)看下initialize調(diào)用相關(guān)的源碼。objc的庫(kù)里有一個(gè)_class_initialize方法實(shí)現(xiàn),如下:
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
BOOL reallyInitialize = NO;
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
monitor_enter(&classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
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]",
cls->nameForLogging());
}
monitor_enter(&classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
monitor_exit(&classInitLock);
return;
}
else if (cls->isInitializing()) {
if (_thisThreadIsInitializingClass(cls)) {
return;
} else {
monitor_enter(&classInitLock);
while (!cls->isInitialized()) {
monitor_wait(&classInitLock);
}
monitor_exit(&classInitLock);
return;
}
}
else if (cls->isInitialized()) {
return;
}
else {
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
在這段代碼里,我們能看到initialize的調(diào)用順序和線程安全性。