問題一:Category
中有l(wèi)oad方法嗎抽活?
答:有的炊甲,但是和其他方法不同渠鸽,Category 中的load
和類中的load
方法,并不是簡單的覆蓋或者繼承娇未。用一個簡單的工程可以看出:
LoadClass.m:
#import "LoadClass.h"
@implementation LoadClass
+(void)load{
NSLog(@"Class Load Execute");
}
@end
LoadClass+LoadClassCategory.m:
#import "LoadClass+LoadClassCategory.h"
@implementation LoadClass (LoadClassCategory)
+(void)load{
NSLog(@"Category Load Execute");
}
@end
輸出:
2019-11-10 20:57:22.986038+0800 LoadDemo[88431:1086125] Class Load Execute
2019-11-10 20:57:22.986486+0800 LoadDemo[88431:1086125] Category Load Execute
從輸出可以看出,并沒有出現(xiàn)方法覆蓋的情況星虹,兩個load
方法都被執(zhí)行了零抬。
問題二:load方法是什么時候被調(diào)到的?
要探究這個問題其實很簡單搁凸,只需要在上述代碼中的load
方法中下一個斷點媚值,查看調(diào)用堆棧即可:
程序運行并命中斷點之后,查看輸出:
這里我們主要看
load
的上層調(diào)用load_images
护糖。顯然這個時候褥芒,我們需要借助runtime源碼
去分析。首先找到這個函數(shù):
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
函數(shù)內(nèi)部其實很簡單嫡良,就是幾個函數(shù)的調(diào)用锰扶,這里,我們主要關注兩個函數(shù)-----prepare_load_methods
寝受、call_load_methods
坷牛。
先看prepare_load_methods
:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
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);
}
}
看函數(shù)的內(nèi)部流程和調(diào)用,其實不難看出很澄,prepare_load_methods
函數(shù)內(nèi)部遍歷所有類京闰,并調(diào)用schedule_class_load
函數(shù),將所有類中的load
方法加入列表中甩苛,之后有遍歷所有的category
并調(diào)用add_category_to_loadable_list
函數(shù)將category
中的load
方法加入列表中蹂楣。大致的流程就是這樣子的,如果想更深入的研究load
方法添加到列表中的細節(jié)讯蒲,可以詳細分析一下這兩個函數(shù)痊土。
接下來看看call_load_methods
是怎么調(diào)用load
方法的。
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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;
}
根據(jù)代碼和注釋墨林,可以清楚的看出赁酝,load
方法的調(diào)用流程為:先調(diào)用類中的load
方法犯祠,再調(diào)用類別中的load
方法。這其實可以說明一個問題:類中的load
方法比類別中的load
方法先執(zhí)行酌呆。
到這里衡载,其實咱們就可以回答剛剛的那個問題了:
答:load
方法是在類或者類別添加到runtime
的時候,被調(diào)用的肪笋,而且其調(diào)用順序是:先類中的load
月劈,再類別中的load
。