+load 方法解析
分析load方法前先來(lái)做一個(gè)小測(cè)驗(yàn):
// Animal 類(lèi)
@interface Animal : NSObject
@end
@implementation Animal
+ (void)load {
NSLog(@"Animal -- load");
}
@end
// Dog 類(lèi) (->superclass animal)
@interface Dog : Animal
@end
@implementation Dog
+ (void)load {
NSLog(@"Dog -- load");
}
@end
// Cat 類(lèi) (->superclass animal)
@interface Cat : Animal
@end
@implementation Cat
+ (void)load {
NSLog(@"Cat -- load");
}
// Animal 分類(lèi)
@interface Animal (Eat)
@end
@implementation Animal (Eat)
+ (void)load {
NSLog(@"Animal (Eat) -- load");
}
@end
// Dog 分類(lèi)
@interface Dog (Eat)
@end
@implementation Dog (Eat)
+ (void)load {
NSLog(@"Dog (Eat) -- load");
}
@end
// Cat 分類(lèi)
@interface Cat (Eat)
@end
@implementation Cat (Eat)
+ (void)load {
NSLog(@"Cat (Eat) -- load");
}
@end
我們來(lái)運(yùn)行一下程序蔓榄,發(fā)現(xiàn)沒(méi)有調(diào)用這些類(lèi)但是load方法都加載了,加載的結(jié)果如下:
2018-07-22 15:36:46.369503+0800 debug-objc[11354:638134] Animal -- load
2018-07-22 15:36:46.369954+0800 debug-objc[11354:638134] Cat -- load
2018-07-22 15:36:46.369965+0800 debug-objc[11354:638134] Dog -- load
2018-07-22 15:36:46.369981+0800 debug-objc[11354:638134] Cat (Eat) -- load
2018-07-22 15:36:46.369996+0800 debug-objc[11354:638134] Dog (Eat) -- load
2018-07-22 15:36:46.370009+0800 debug-objc[11354:638134] Animal (Eat) -- load
上一篇中我們講到了category的原理,猜想這里的結(jié)果應(yīng)該是只會(huì)調(diào)用分類(lèi)的方法,可是為什么這里都調(diào)用了呢?還有看一下 PROJECT->TARGETS->Build Phases->Compole Source
的編譯順序盈滴,分類(lèi)的編譯順序和調(diào)用順序能對(duì)上,但是cat和dog都在animal的前面轿钠,為什么會(huì)是先調(diào)用的animal的load方法呢?
我們來(lái)分析一下load方法在runtime的源碼巢钓,老規(guī)矩,先下載runtime源碼疗垛,找到入口 _objc_init
的 load_images
方法症汹,看一下上面的官方注解:Process +load in the given images which are being mapped in by dyld.
,可以發(fā)現(xiàn)load方法就是在這里面加載的贷腕,看源碼:
load_images(const char *path __unused, const struct mach_header *mh) {
/**
* 準(zhǔn)備 load methods
*/
prepare_load_methods((const headerType *)mh);
/**
* 調(diào)用 load methods
*/
call_load_methods();
}
這兩個(gè)方法我們拿出來(lái)單獨(dú)看一下:
a> prepare_load_methods
:
void prepare_load_methods(const headerType *mhdr) {
/**
* 加載所有類(lèi)
*/
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
/**
* 調(diào)用 class 的 load 方法
*/
schedule_class_load(remapClass(classlist[i]));
}
/**
* 加載所有分類(lèi)
*/
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0背镇; i < count;i++) {
category_t *cat = categorylist[i]泽裳;
/**
* 將category中的load方法加載到loadable的列表中
*/
add_category_to_loadable_list(cat);
}
}
可以看到瞒斩,這里永遠(yuǎn)都是先加載類(lèi)的load方法然后在加載分類(lèi)的load方法,正好解釋我們上面的小測(cè)試的結(jié)果涮总,類(lèi)的load方法會(huì)先與分類(lèi)被調(diào)用胸囱。再把 schedule_class_load
方法拿出來(lái)看一下:
static void schedule_class_load(Class cls) {
/**
* 判斷如果已經(jīng)加載過(guò)load方法則直接返回
*/
if (cls->data()->flags & RW_LOADED) return;
/**
* Ensure superclass-first ordering
* 這里是遞歸調(diào)用,優(yōu)先加載 class的superclass的load方法瀑梗,直到superclass為空
*/
schedule_class_load(cls->superclass);
/**
* 將類(lèi)的load方法加載到loadable的類(lèi)表中
*/
add_class_to_loadable_list(cls);
/**
* 設(shè)置此類(lèi)的load方法標(biāo)志位烹笔,表示已經(jīng)加載過(guò)load方法
*/
cls->setInfo(RW_LOADED);
}
這里的調(diào)用非常巧妙,用遞歸調(diào)用優(yōu)先加載superclass的load方法抛丽,這里就明白了原來(lái)在加載類(lèi)的load方法時(shí)會(huì)優(yōu)先加載superclass的load方法谤职,到這里明白了上面的小測(cè)驗(yàn)為什么先回調(diào) animal 的load方法了嗎?
再看一下 add_class_to_loadable_list
方法:
void add_class_to_loadable_list(Class cls) {
IMP method;
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
/**
* 這里是將cls的method方法存放到 loadable_classes 數(shù)組中亿鲜,
* loadable_classes_used++ 可以看出這里是按編譯時(shí)的順序加載的
*/
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
注意:上面獲取 method 的時(shí)候是獲取的load method 的 IMP
允蜈。
prepare_load_methods
的分類(lèi)加載的時(shí)候調(diào)用的就是 add_class_to_loadable_list
方法,所以分類(lèi)的加載順序就是按編譯的順序加載的蒿柳。
到這里所有的類(lèi)和分類(lèi)的數(shù)據(jù)load完畢饶套,并且加載順序也已經(jīng)確定。
b> call_load_methods
:
void call_load_methods(void) {
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);
}
/**
* call_class_loads 方法
*/
static void call_class_loads(void) {
int i;
/**
* loadable_classes 就是我們上面 prepare 的加載完成的數(shù)據(jù)
*/
struct loadable_class *classes = loadable_classes;
/**
* i ++ 可以看到這里是按著順序加載我們?cè)?prepare 時(shí)準(zhǔn)備的數(shù)據(jù)的
*/
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
/**
* 調(diào)用 load 方法
*/
(*load_method)(cls, SEL_load);
}
}
看上面的call_load_methods方法其馏,先加載類(lèi)在加載分類(lèi)凤跑,在加載類(lèi)的方法 call_class_loads
中是按著我們?cè)?prepare_load_methods
方法中準(zhǔn)備好的數(shù)據(jù)順序執(zhí)行的,所有prepare時(shí)加載的順序就是load調(diào)用的順序叛复,可以查看分類(lèi)的 call_category_loads
方法,跟加載類(lèi)的方式是一樣的。
到這里類(lèi)和分類(lèi)的所有l(wèi)oad方法調(diào)用完畢褐奥。
關(guān)于load方法總結(jié)
:
- 調(diào)用方式:根據(jù)函數(shù)的 IMP 直接調(diào)用咖耘。
- 調(diào)用時(shí)機(jī):在runtime加載類(lèi)、分類(lèi)時(shí)調(diào)用(只調(diào)用一次)
- 調(diào)用順序:"1撬码、a> 先調(diào)用類(lèi)的load(優(yōu)先編譯的優(yōu)先調(diào)用)" "b> 調(diào)用子類(lèi)的load方法之前會(huì)先調(diào)用父類(lèi)的load方法" "1儿倒、a> 再調(diào)用分類(lèi)的load方法(優(yōu)先編譯的分類(lèi)優(yōu)先調(diào)用)"
initialize 方法
我們知道 + initialize 方法會(huì)在類(lèi)的第一次接收消息時(shí)調(diào)用。
將上面的小測(cè)試的所有l(wèi)oad替換為initialize呜笑,然后分別調(diào)用如下:
第一次調(diào)用:
[Animal class];
打印結(jié)果:
2018-07-22 16:52:15.039883+0800 debug-objc[12499:710327] Animal (Eat) -- initialize
第二次調(diào)用:
[Dog class];
打印結(jié)果:
2018-07-22 16:53:35.175441+0800 debug-objc[12534:711843] Animal (Eat) -- initialize
2018-07-22 16:53:35.175637+0800 debug-objc[12534:711843] Dog (Eat) -- initialize
第三次調(diào)用:
[Animal class];
[Dog class];
打印結(jié)果:
2018-07-22 16:54:04.664353+0800 debug-objc[12553:712521] Animal (Eat) -- initialize
2018-07-22 16:54:04.664578+0800 debug-objc[12553:712521] Dog (Eat) -- initialize
看到三次的打印結(jié)果可以推測(cè) initialize
的方法的調(diào)用符合我們上一篇文章中講解夫否,最后加載的分類(lèi)會(huì)優(yōu)先類(lèi)調(diào)用,并且類(lèi)的方法不會(huì)再調(diào)用叫胁,第二次的調(diào)用可以推測(cè)在調(diào)用 initialize
時(shí)會(huì)先調(diào)用父類(lèi)的 initialize
方法凰慈。
下面我們來(lái)把子類(lèi)Dog和dog分類(lèi)的 initialize
方式刪除,再來(lái)打印看看結(jié)果:
調(diào)用:
[Dog class];
打印結(jié)果:
2018-07-22 17:08:29.497283+0800 debug-objc[12830:725499] Animal (Eat) -- initialize
2018-07-22 17:08:29.497558+0800 debug-objc[12830:725499] Animal (Eat) -- initialize
為什么 Animnal 的 initialize
會(huì)出現(xiàn)兩次的調(diào)用呢驼鹅? initialize
不是只在第一次接收消息時(shí)調(diào)用嗎微谓?
initialize 是在發(fā)送消息時(shí)調(diào)用的,所以我們找到 objc_msgSend
方法输钩,最終找到 class_getInstanceMethod
方法豺型,根據(jù)調(diào)用:->class_getInstanceMethod -> lookUpImpOrNil ->lookUpImpOrForward
下面我們來(lái)分析一下runtime的源碼:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver) {
/**
* 如果cls還沒(méi)有 Initialized 調(diào)用 _class_initialize
*/
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
}
void _class_initialize(Class cls) {
Class supercls;
/**
* 遞歸調(diào)用 cls父類(lèi)的 callInitialize
*/
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
/**
* 沒(méi)有 initialize 時(shí)設(shè)置 reallyInitialize為true
*/
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
if (reallyInitialize) {
/**
* 調(diào)用返送 SEL_initialize 消息方法
*/
callInitialize(cls);
}
}
/**
* 發(fā)送 SEL_initialize 消息
*/
void callInitialize(Class cls) {
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
}
分析源碼可以看到,initialize
方法會(huì)先去查找父類(lèi)买乃,如果父類(lèi) initialize
沒(méi)有調(diào)用過(guò)姻氨,會(huì)先向父類(lèi)發(fā)送 SEL_initialize
消息,這里可以解釋一下我們上面的小測(cè)試為什么 animal 的方法調(diào)用了兩次剪验,因?yàn)樵?_class_initialize
方法中第一次由父類(lèi) animal 發(fā)送 SEL_initialize
消息哼绑,第二次由 dog 類(lèi)發(fā)送 SEL_initialize
消息,而由于 dog 類(lèi)沒(méi)有 initialize
方法碉咆,所以會(huì)去調(diào)用父類(lèi) animal 的方法抖韩。
關(guān)于initialize方法總結(jié)
:
- 調(diào)用方式:initialize是通過(guò)objc_msgSend調(diào)用。
- 調(diào)用時(shí)機(jī):initialize時(shí)類(lèi)在第一次接收到消息時(shí)調(diào)用疫铜,每個(gè)類(lèi)只會(huì)initialize一次(父類(lèi)的initialize方法可能會(huì)被多次調(diào)用)茂浮。
- 調(diào)用順序:先初始化父類(lèi) 再初始化子類(lèi)(可能最終調(diào)用的是父類(lèi)的initialize方法)