+load 和 +initialize 方法你們確定不陌生,咱們的項目中會有不少重寫這兩個方法的地方,可是你有沒有想過他們有什麼區別?產生區別的緣由是什麼?今天咱們就從源碼的層面來解答一下這些問題。數組
閱讀文章時請注意:留意源碼中我增長註釋的部分,每每是該函數的重點部分。markdown
針對 load 和 initialize 方法,咱們先來思考幾個問題:app
因爲源碼較多,將所有源碼貼出來不太現實,所以最好的方法是下載runtime源碼,跟着文章一塊兒閱讀。函數
load方法的調用時機是runtime加載類和分類的時候調用。
咱們跟上一篇文章同樣,定義一個Person類和Person的兩個分類ui
// Person
+ (void)load {
NSLog(@"person");
}
+ (void)test {
NSLog(@"person test");
}
// Person + Eat
+ (void)load {
NSLog(@"Person Eat");
}
+(void)test {
NSLog(@"Person(Eat) test");
}
2020-06-01 19:25:49.718843+0800 TestCategory[38073:13350285] person
2020-06-01 19:25:49.719425+0800 TestCategory[38073:13350285] Person Eat
2020-06-01 19:25:49.895610+0800 TestCategory[38073:13350285] Person(Eat) test
複製代碼
能夠看到,load方法不論分類仍是原類都會被調用,且每一個類只會被調用一次,可是test方法卻只調用了一次。this
test方法只調用一次的緣由在上一篇文章中已經解釋了,分類的方法會放到list的前面,經過msgSend調用方法時會先查找到分類的方法。
load方法爲何會調用兩次呢?咱們仍是要從runtime源碼中尋找答案。spa
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// 判斷是否Categories已經被attach,attach相關詳見上期
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// 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
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
// 1. 重複的調用 類 的+load方法直到全部類的load方法都被調用完
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
// 2. 對每一個 分類 調用一次+load方法
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 all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
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;
// typedef void(*load_method_t)(id, SEL);
// load_method_t是一個指針類型,這裏建立一個指針指向該類的load方法的地址
// 能夠看到這裏classes其實是上面的loadable_classes
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方法的地方,能夠看到是經過地址找到相應的函數後調用
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
複製代碼
這裏就破案了ssr
經過上面的代碼能夠看出,+load 方法的調用不是經過msgSend,而是直接找到該類 +load 方法的地址,經過地址來調用 +load 方法,所以不會出現只調分類的 +load 方法而不調原類的 +load 方法的狀況。指針
咱們一共建立4個類,分別是code
通過屢次實驗獲得以下結論:
爲了搞清楚這個結論的緣由,咱們仍是要從runtime的源碼入手。
在上面的 call_class_loads 函數中,是從 loadable_classes 中將函數地址一個一個取出來的,所以咱們須要搞清 loadable_classes 是怎麼來的,以及他的順序是怎麼樣的。
因爲是從 call_class_loads 函數一步一步往回找,所以這裏的調用順序是倒序的,你們要注意這點。
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
// 若是沒有實現 +load 方法,那麼不須要添加到list中
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
// 能夠看到當list容量不夠時,擴充的邏輯是 *2 + 16
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// 將class存放到list中,並將method賦值給對應的cls
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
複製代碼
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
// #define RW_LOADED (1<<23)
// 這裏目測是某個標誌位,猜想表明已經加入到了list
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);
}
複製代碼
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 取到classList
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 執行schedule_class_load,將有 +load 方法的class加入到 loadable_classes 中
schedule_class_load(remapClass(classlist[i]));
}
// 分類相關操做,這裏暫時不看
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
複製代碼
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// 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
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
至此,咱們已經把完整的鏈路打通了,prepare_load_methods 的調用方是 load_images,而 load_images 的調用方是是 objc_init ,咱們來理一下方法的調用順序與做用
那麼原類的 +load 方法調用過程的源碼咱們已經分析完了,接下來讓咱們來看看分類的 +load 方法的調用過程是怎麼樣的
仍是先看一下 prepare_load_methods(const headerType *mhdr) 函數:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 原類
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 分類
// _getObjc2NonlazyCategoryList 函數取出了全部分類,這裏沒有作任何順序上的調整,所以是按照編譯順序直接拿到數組
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
// 經過for循環,直接將 categorylist 中的分類加入到loadable_categories中
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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
// 添加到 loadable_categories 列表中
add_category_to_loadable_list(cat);
}
}
複製代碼
所以能夠知道,分類不會受子父類的關係影響,單純的就是誰先編譯誰先調用 +load 方法,咱們能夠對 Person(Eat) 和 Student(Eat) 調整編譯順序,來驗證咱們的結論。
以下面兩幅圖:
先說結論:
用上文中的Person, Person(Eat), Student, Student(Eat) 就能夠進行驗證,這裏就不將驗證過程放出來了。
從打印的結果來看是這樣的,可是咱們須要經過源碼進行探究。
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// 能夠看到這裏是核心
return class_getInstanceMethod(cls->getMeta(), sel);
}
複製代碼
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
Method meth;
// 此時尚未給這個cls發過消息,所以不會在cache中找到
meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}
// Search method lists, try method resolver, etc.
// 這裏是核心邏輯,注意這裏傳遞的第四個參數是 LOOKUP_INITIALIZE | LOOKUP_RESOLVER
lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}
return _class_getMethod(cls, sel);
}
複製代碼
// 能夠看出 behavior & LOOKUP_INITIALIZE 是真, !cls->isInitialized() 也是真,由於沒有調用過 initialized 方法
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
// 因此能夠走到if判斷裏,這裏是核心邏輯
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
複製代碼
initializeNonMetaClass(nonmeta);
複製代碼
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(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;
// 這裏也是遞歸調用
// 這裏在找是否有super class,而且super class的initialize方法是否被執行過,若是沒執行過,遞歸調用傳遞super class
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// 這裏是真正調用initialize方法的地方
callInitialize(cls);
}
複製代碼
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
複製代碼
終於,真相大白了,咱們看到真正調用 +initialize 方法的地方,是經過 objc_msgSend 函數,這也就解釋了爲何會調用到分類的 +initialize 方法,詳情見上一篇文章。
咱們也終於驗證了,父類的 +initialize 方法會先調用,且只會被調用到一次。
但願你們多多上手嘗試,不要以爲看過了就是會了,對於知識須要深層次的挖掘,淺嘗輒止是沒有用的。
文中若有錯誤,歡迎指出。