Load和Initialize實現原理
Load和Initialize實現原理
+Load實現原理
+load方法會在 runtime
載入 類
、 分類
時呼叫
每個類、分類的+load,在程式執行過程中 只調用一次
+load方法是根據方法 地址
直接呼叫,並不是經過objc_msgSend函式呼叫
呼叫順序
- 1、先呼叫類的+load
- 按照編譯先後順序呼叫(先編譯,先呼叫)
- 呼叫子類的+load之前會先呼叫父類的+load
- 2、再呼叫分類的+load
- 按照編譯先後順序呼叫(先編譯,先呼叫)
objc4原始碼解讀過程
objc-os.mm 檔案
- _objc_init
- load_images
- prepare_load_methods
- schedule_class_load
- add_class_to_loadable_list
- add_category_to_loadable_list
- call_load_methods
- call_class_loads
- call_category_loads
_objc_init
方法是RunTime執行的入口
void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? environ_init(); tls_init(); static_init(); lock_init(); exception_init(); _dyld_objc_notify_register(↦_images, load_images, unmap_image); }
小知識: images
是映象的意思
我們在 _objc_init
方法中找到 load_images
, load_images
是Load載入映象的意思,所有我們可以猜測這個裡面應該有我們load的載入方法的相關實現
我們點選進入 load_images
方法
load_images(const char *path __unused, const struct mach_header *mh) { // Return without taking locks if there are no +load methods here. if (!hasLoadMethods((const headerType *)mh)) return; recursive_mutex_locker_t lock(loadMethodLock); // Discover load methods { rwlock_writer_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } // Call +load methods (without runtimeLock - re-entrant) call_load_methods(); }
裡面有兩個需要我們注意的
prepare_load_methods((const headerType *)mh) call_load_methods()
我們點選進入 prepare_load_methods((const headerType *)mh)
準備載入Load方法
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); } }
我們可以看到執行順序
- 1、
schedule_class_load(remapClass(classlist[i]));
,這個是把類中的Load
方法新增到陣列中 - 2、
add_category_to_loadable_list(cat);
這個是把分類中的load
方法新增到陣列中
檢視類的load方法
我們檢視 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); add_class_to_loadable_list(cls);
走到這裡我們大概是清楚了類中load方法的載入新增過程,就是先把 父類新增帶陣列中,然後再把自己新增到陣列中
檢視分類的load方法
我們點選 add_category_to_loadable_list(cat)
進入檢視方法實現
void add_category_to_loadable_list(Category cat) { IMP method; loadMethodLock.assertLocked(); method = _category_getLoadMethod(cat); // Don't bother if cat has no +load method if (!method) return; if (PrintLoading) { _objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat)); } if (loadable_categories_used == loadable_categories_allocated) { loadable_categories_allocated = loadable_categories_allocated*2 + 16; loadable_categories = (struct loadable_category *) realloc(loadable_categories, loadable_categories_allocated * sizeof(struct loadable_category)); } loadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; loadable_categories_used++; }
loadable_categories_used++;
分類沒有什麼特殊的方法,應該就是按照編譯順序新增到陣列的。
實現
我們剛才看到了類分類中的新增順序,我們在來看看載入順序
點選 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; }
上面直接有官方文件給我們的順序
call_class_loads(); more_categories = call_category_loads()
Demo
我們這裡來一個測試demo
- 父類
Person
Person+Run.h Person+Eat
- 2、子類
Student Teacher
編譯順序

image
列印順序

image
- 1、先編譯
Teacher
但是最先列印Person
- 2、分類
Person+Run
在子類Student
之前編譯,但是列印確實先列印Student
所有上面總結是十分準確的
- 1、先呼叫類的+load
- 按照編譯先後順序呼叫(先編譯,先呼叫)
- 呼叫子類的+load之前會先呼叫父類的+load
- 2、再呼叫分類的+load
- 按照編譯先後順序呼叫(先編譯,先呼叫)
我們是否注意到了另一個問題,為什麼在有分類的時候還載入類的 load
方法,不應該是分類覆蓋類嗎?
我們在檢視 load
的原始碼實現的時候發現,+load方法是根據方法 地址
直接呼叫,並不是經過 objc_msgSend
函式呼叫,如果使用 objc_msgSend
會出現分類覆蓋類,但是 load
直接是根據 指標
找方法的,所以不會覆蓋。
Initialize實現原理
+initialize方法會在類 第一次接收到訊息
時呼叫
呼叫順序
- 先呼叫父類的+initialize,再呼叫子類的+initialize(先初始化父類,再初始化子類,每個類只會初始化1次)
- +initialize和+load的很大區別是,+initialize是通過objc_msgSend進行呼叫的,所以有以下特點
- 如果子類沒有實現+initialize,會呼叫父類的+initialize(所以父類的+initialize可能會被呼叫多次)
- 如果分類實現了+initialize,就覆蓋類本身的+initialize呼叫
objc4原始碼解讀過程
objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)
我們在 objc-runtime-new.mm
檔案中找到 class_getInstanceMethod
,裡面就有一個主要實現方法 lookUpImpOrNil
Method class_getInstanceMethod(Class cls, SEL sel) { if (!cls||!sel) return nil; #warning fixme build and search caches lookUpImpOrNil(cls, sel, nil, NO/*initialize*/, NO/*cache*/, YES/*resolver*/); #warning fixme build and search caches return _class_getMethod(cls, sel); }
裡面沒有什麼實現我們繼續點選 lookUpImpOrNil
進入實現
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); if (imp == _objc_msgForward_impcache) return nil; else return imp; }
裡面好像還是沒有我們想要的具體實現,繼續點選 lookUpImpOrForward
檢視實現
if (initialize&&!cls->isInitialized()) { runtimeLock.unlockRead(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.read(); }
這個裡面有一個 if
判斷裡面有一些東西,就是在沒有實現 isInitialized
的時候,呼叫 _class_initialize
方法,我們點選進入檢視相關實現
if (supercls&&!supercls->isInitialized()) { _class_initialize(supercls); }
callInitialize(cls);
裡面有這兩個主要的函式
initialize initialize
我們在點選 callInitialize
發現具體是通過 objc_msgSend
來實現的。
void callInitialize(Class cls) { ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); asm(""); }
Demo
測試案例1
我們建立父類 Person
,然後建立子類 Student
& Teacher
,子類不實現 initialize
方法,父類實現該方法
[Teacher alloc]; [Student alloc];

image
結果列印三次 [Person initialize]
方法,列印一次我們是能夠想到了,因為實現過程是先看看父類有沒有已經實現,如果沒有實現就先實現父類的。但是另外兩次是怎麼來的呢。
[Student alloc]
的實現大概是這樣的
objc_msgSend([Person class], @selector(initialize)); objc_msgSend([Student class], @selector(initialize));
- 1、第一步就是實現父類的
initialize
方法 - 2、第二步,Student先查詢自己元類有沒有
initialize
方法,如果自己元類沒有實現,就向上查詢父類元類有沒有initialize
方法,如果有就執行,沒有繼續向上查詢
測試案例2
我們建立父類 Person
,然後建立分類 Person+Eat
,都是實現 initialize
方法
[Person alloc];

image
這句程式碼就是證明了 如果分類實現了+initialize,就覆蓋類本身的+initialize呼叫
總結
load、initialize方法的區別什麼?
- 1.呼叫方式
- 1> load是根據函式地址直接呼叫
- 2> initialize是通過objc_msgSend呼叫
-2.呼叫時刻
- 1> load是runtime載入類、分類的時候呼叫(只會呼叫1次)
- 2> initialize是類第一次接收到訊息的時候呼叫,每一個類只會initialize一次(父類的initialize方法可能會被呼叫多次)
load、initialize的呼叫順序?
1.load
-
1> 先呼叫類的load
- a) 先編譯的類,優先呼叫load
- b) 呼叫子類的load之前,會先呼叫父類的load
-
2> 再呼叫分類的load
- a) 先編譯的分類,優先呼叫load
2.initialize
- 1> 先初始化父類
- 2> 再初始化子類(可能最終呼叫的是父類的initialize方法)