category原理探究-2

+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_initload_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é)

  1. 調(diào)用方式:根據(jù)函數(shù)的 IMP 直接調(diào)用咖耘。
  1. 調(diào)用時(shí)機(jī):在runtime加載類(lèi)、分類(lèi)時(shí)調(diào)用(只調(diào)用一次)
  1. 調(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é)

  1. 調(diào)用方式:initialize是通過(guò)objc_msgSend調(diào)用。
  1. 調(diào)用時(shí)機(jī):initialize時(shí)類(lèi)在第一次接收到消息時(shí)調(diào)用疫铜,每個(gè)類(lèi)只會(huì)initialize一次(父類(lèi)的initialize方法可能會(huì)被多次調(diào)用)茂浮。
  1. 調(diào)用順序:先初始化父類(lèi) 再初始化子類(lèi)(可能最終調(diào)用的是父類(lèi)的initialize方法)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市壳咕,隨后出現(xiàn)的幾起案子席揽,更是在濱河造成了極大的恐慌,老刑警劉巖谓厘,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幌羞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡竟稳,警方通過(guò)查閱死者的電腦和手機(jī)属桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)熊痴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人聂宾,你說(shuō)我怎么就攤上這事果善。” “怎么了系谐?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵巾陕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我纪他,道長(zhǎng)鄙煤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任茶袒,我火速辦了婚禮梯刚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弹谁。我一直安慰自己乾巧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布预愤。 她就那樣靜靜地躺著沟于,像睡著了一般。 火紅的嫁衣襯著肌膚如雪植康。 梳的紋絲不亂的頭發(fā)上旷太,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音销睁,去河邊找鬼供璧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冻记,可吹牛的內(nèi)容都是我干的睡毒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冗栗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼演顾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隅居,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钠至,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后胎源,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體棉钧,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年涕蚤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪卿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片的诵。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖愧捕,靈堂內(nèi)的尸體忽然破棺而出奢驯,到底是詐尸還是另有隱情申钩,我是刑警寧澤次绘,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站撒遣,受9級(jí)特大地震影響邮偎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜义黎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一禾进、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廉涕,春花似錦泻云、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至层释,卻和暖如春婆瓜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贡羔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工廉白, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乖寒。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓猴蹂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親楣嘁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子磅轻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評(píng)論 0 9
  • 一马澈、基本使用 1瓢省、RevanPerson類(lèi) 2、RevanPerson+RevanRun 3痊班、RevanPerso...
    紫荊秋雪_文閱讀 586評(píng)論 0 1
  • iOS底層原理總結(jié) - Category的本質(zhì) 面試題 Category的實(shí)現(xiàn)原理勤婚,以及Category為什么只能...
    xx_cc閱讀 30,455評(píng)論 36 199
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • 簡(jiǎn)書(shū)就是一個(gè)我可以隨便發(fā)泄任何心情的地方,這里沒(méi)人認(rèn)識(shí)你涤伐,沒(méi)人知道你馒胆,你可以隨便的肆意發(fā)瘋缨称,罵街也好,撒潑也罷祝迂。也...
    問(wèn)水傲血閱讀 204評(píng)論 0 0