我們知道,iOS App的 main() 函數(shù)位于 main.m 中,這是我們熟知的程序入口,但是在這之前, 還要先進(jìn)行加載 framework 、初始化 runtime 等操作, framework 的加載是由 dylb 調(diào)用的,關(guān)于 dylb ,這里不作探討,感興趣的同學(xué)可以看看這篇文章(dyld: Dynamic Linking On OS X )。本文結(jié)合 runtime 源碼,對(duì)類的加載和初始化中涉及到的兩個(gè)方法 +load和+initialize 進(jìn)行探討,了解他們的調(diào)用時(shí)機(jī)和調(diào)用方式,
所用到的runtime源碼為objc4-680.tar.gz 。
load
runtime 的初始化函數(shù)在 objc-os.mm 中的 _objc_init 中, 這個(gè)方法在 old-ABI 中是由 dylb 在初始化動(dòng)態(tài)鏈接庫(kù)的時(shí)候調(diào)用的,現(xiàn)在是由 libSystem 在動(dòng)態(tài)鏈接庫(kù)初始化之前調(diào)用的:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Old ABI: called by dyld as a library initializer
* New ABI: called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
......
// Register for unmap first, in case some +load unmaps something
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}
在這里, runtime 先會(huì)調(diào)用 map_2_images 方法, 加載 images 到內(nèi)存中, category 中擴(kuò)展的屬性和方法就是在這個(gè)時(shí)候附加到類上的,這將在后續(xù)的文章中進(jìn)行探討。然后會(huì)調(diào)用 load_images 這個(gè)方法:
const char *load_images(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[])
{
bool found;
// Return without taking locks if there are no +load methods here.
found = false;
......
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
//先load images
found = load_images_nolock(state, infoCount, infoList);
}
// Call +load methods (without runtimeLock - re-entrant)
if (found) {
//然后調(diào)用類的+load方法
call_load_methods();
}
return nil;
}
load_images 在這個(gè)方法里先是調(diào)用 load_images_nolock 方法, 在這個(gè)方法里會(huì)調(diào)用 prepare_load_methods 方法去準(zhǔn)備好要被調(diào)用的 +load 方法,我們先來(lái)看下 prepare_load_methods 方法的實(shí)現(xiàn):
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &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(Class cls) 方法去準(zhǔn)備好所有滿足 +load 方法調(diào)用條件的類,這個(gè)方法會(huì)對(duì)入?yún)⒌母割愡M(jìn)行遞歸調(diào)用,以確保父類優(yōu)先的順序:
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);
}
當(dāng) prepare_load_methods 函數(shù)執(zhí)行完之后,所有滿足 +load 方法調(diào)用條件的類和分類就被分別保存在全局變量 loadable_classes 和 loadable_categories 中了。
準(zhǔn)備好類和分類之后,接下來(lái)就是對(duì)他們的 +load 方法進(jìn)行調(diào)用了,找到 call_load_methods 方法:
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
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;
}
在這個(gè)方法里,會(huì)以類優(yōu)先于分類的順序調(diào)用 +load 方法
這里有兩個(gè)關(guān)鍵的函數(shù) call_class_loads() 和 call_category_loads
這兩個(gè)函數(shù)會(huì)遍歷上一步中準(zhǔn)備好的 loadable_classes 和 loadable_categories 的 +load 方法,需要注意的是他們都是以函數(shù)內(nèi)存地址的方式 ((*load_method)(cls, SEL_load)) 對(duì) +load 方法進(jìn)行調(diào)用的,而不是使用發(fā)送消息 objc_msgSend 的方式.
這樣的調(diào)用方式就使得
+load方法擁有了一個(gè)特性,那就是子類、父類和分類中的+load方法的實(shí)現(xiàn)是被區(qū)別對(duì)待的。也就是說(shuō)如果子類沒(méi)有實(shí)現(xiàn) +load 方法,那么當(dāng)它被加載時(shí)runtime是不會(huì)去調(diào)用父類的+load方法的。同理,當(dāng)一個(gè)類和它的分類都實(shí)現(xiàn)了+load方法時(shí),兩個(gè)方法都會(huì)被調(diào)用,多個(gè)分類實(shí)現(xiàn)了+load方法時(shí),每個(gè)分類的+load方法都會(huì)被調(diào)用。
initialize
+initialize 方法是在類或類的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用。
也就是說(shuō) +initialize 方法是以懶加載的方式被調(diào)用的,如果一直沒(méi)有給一個(gè)類或他的子類發(fā)送消息,那么這個(gè)類的 +initialize 方法是永遠(yuǎn)不會(huì)調(diào)用的。
當(dāng)我們向某個(gè)類發(fā)送消息時(shí),runtime 會(huì)調(diào)用 IMP lookUpImpOrForward(...) 這個(gè)函數(shù)在類中查找相應(yīng)方法的實(shí)現(xiàn)或進(jìn)行消息轉(zhuǎn)發(fā),打開(kāi) objc-runtime-new.h 找到這個(gè)函數(shù):
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
...
if (initialize && !cls->isInitialized()) {
// 類沒(méi)有初始化時(shí),對(duì)類進(jìn)行初始化
_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
}
...
}
從中可以看到當(dāng)類沒(méi)有初始化時(shí),會(huì)調(diào)用 _class_initialize(Class cls) 對(duì)類進(jìn)行初始化:
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;
// 遞歸調(diào)用,對(duì)父類進(jìn)行_class_initialize調(diào)用,確保父類的initialize方法比子類先調(diào)用
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
......
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());
}
// 發(fā)送調(diào)用類方法initialize的消息
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
......
}
在這里,先是對(duì)入?yún)⒌母割愡M(jìn)行遞歸調(diào)用,以確保父類優(yōu)先于子類初始化
還有一個(gè)關(guān)鍵的地方: runtime 使用了發(fā)送消息 objc_msgSend 的方式對(duì) +initialize 方法進(jìn)行調(diào)用,這樣, +initialize 方法的調(diào)用就與普通方法的調(diào)用是一致的,都是走的發(fā)送消息的流程,所以,如果子類沒(méi)有實(shí)現(xiàn) +initialize 方法,將會(huì)沿著繼承鏈去調(diào)用父類的 +initialize 方法,同理,分類中的 +initialize 方法會(huì)覆蓋原本類的方法。
雖然對(duì)每個(gè)類只會(huì)調(diào)用一次 _class_initialize(Class cls) 方法,但是由于 +initialize 方法的調(diào)用走的是消息發(fā)送的流程,當(dāng)某個(gè)類有多個(gè)子類時(shí),這個(gè)類的 +initialize 方法有可能會(huì)被多次調(diào)用,這時(shí),可能需要在 +initialize 方法中判斷是否是由子類調(diào)用的:
+ (void)initialize{
if (self == [ClassName class]) {
......
}
}
總結(jié):
+load方法在類加載時(shí)被調(diào)用,+initialize方法在類收到第一條消息時(shí)調(diào)用,可能永遠(yuǎn)不會(huì)調(diào)用兩個(gè)方法的調(diào)用順序都是先父類后子類,
+load方法在runtime中是直接以函數(shù)地址的方式進(jìn)行調(diào)用,如果有多個(gè)分類,所有分類的+load方法也會(huì)被調(diào)用,并且在類的+load方法之后被調(diào)用,多個(gè)分類的+load方法調(diào)用順序取決于編譯的順序+initialize方法在runtime中是以發(fā)送消息的方式調(diào)用的,所以子類會(huì)覆蓋父類的實(shí)現(xiàn),分類會(huì)覆蓋類的實(shí)現(xiàn),多個(gè)分類只會(huì)調(diào)用一個(gè)分類的+initialize方法。
參考鏈接
本文出自 kingizz's blog - Objective-C Runtime(三)類的加載與初始化 轉(zhuǎn)載請(qǐng)注明出處。