探索底層原理,積累從點(diǎn)滴做起。大家好,我是Mars。
往期回顧
iOS底層原理探索—OC對象的本質(zhì)
iOS底層原理探索—class的本質(zhì)
iOS底層原理探索—KVO的本質(zhì)
iOS底層原理探索— KVC的本質(zhì)
iOS底層原理探索— Category的本質(zhì)(一)
今天繼續(xù)帶領(lǐng)大家探索iOS之Category的本質(zhì)。
Category中l(wèi)oad方法的調(diào)用
首先我們創(chuàng)建Person
類,并且創(chuàng)建兩個(gè)Person
的分類
我們在
Person
類和兩個(gè)分類中分別實(shí)現(xiàn)load
方法,方法內(nèi)打印輸出類名 -- load
,其它任何操作都不做,直接運(yùn)行查看打印輸出的內(nèi)容:通過打印結(jié)果我們看到,程序在沒有調(diào)用任何方法的情況下,甚至我們都沒有導(dǎo)入類的頭文件,卻執(zhí)行調(diào)用了類內(nèi)的
load
方法,完成了打印。并且打印的第一條為Person
的load
方法。
由此,我們可以得出第一條結(jié)論:
分類中存在
load
方法,而且load
方法在程序啟動時(shí),加載類、分類的時(shí)候就會調(diào)用。在調(diào)用分類的load
方法前,會調(diào)用本類的load
方法。
那么有人可能會問Test1
和Test2
兩個(gè)分類的load
方法執(zhí)行順序呢?其實(shí)這跟程序?qū)︻愇募木幾g順序有關(guān)。我們來看一下我們測試Demo
的編譯順序:
我們可以看到編譯順序?yàn)?code>Test1->
Test2
->Person
->main
,Test1
文件首選編譯,當(dāng)然第一條打印應(yīng)為Person (Test1) -- load
,但是由于在調(diào)用分類的load
方法前,會調(diào)用本類的load
方法,所以第一條打印為Person -- load
,這也恰好驗(yàn)證了我們上面的結(jié)論。
這一點(diǎn)我們通過查看源碼也可得知:
通過紅框標(biāo)注的代碼段可以看出類和分類的
load
方法的調(diào)用順序,而且通過注釋我們可以看出,load
方法只會調(diào)用一次。
然后我們在Person
類的.h
文件中聲明test
類方法,在.m
文件中實(shí)現(xiàn),并且兩個(gè)分類也分別實(shí)現(xiàn),方法內(nèi)打印輸出類名+test方法名
,main
函數(shù)中調(diào)用一下test
類方法,我們看一下打印結(jié)果:
我們看到,此次打印結(jié)果為
Person (Test2) : test
。原因就在于分類中重寫類方法時(shí),分類的類方法會優(yōu)先調(diào)用。我們在上面編譯順序的圖示中可以看到,Person (Test2).m
文件最后編譯,那么就會優(yōu)先調(diào)用Person (Test2)
中的test
方法。這一點(diǎn)我們在iOS底層原理探索— Category的本質(zhì)(一)中有詳細(xì)解讀,不明白的可以調(diào)轉(zhuǎn)查看閱讀。
我們繼續(xù)閱讀源碼,進(jìn)入上文紅框標(biāo)注中的call_class_loads() —— 調(diào)用本類的load方法
以及call_category_loads()——調(diào)用分類的load方法
內(nèi)部查看具體實(shí)現(xiàn):
我們從兩張圖中紅框標(biāo)注的代碼段可以得出第二條結(jié)論:
load
方法的調(diào)用是系統(tǒng)通過類名找到對應(yīng)load
方法的內(nèi)存地址直接調(diào)用的
Category中initialize方法的調(diào)用
我們分別在Person
類和兩個(gè)分類文件中聲明實(shí)現(xiàn)initialize
初始化方法,方法實(shí)現(xiàn)依舊是打印類名+方法名
,然后執(zhí)行[Person alloc]
,運(yùn)行查看打印結(jié)果:
我們繼續(xù)多次執(zhí)行
[Person alloc]
,發(fā)現(xiàn)打印結(jié)果仍舊跟上圖一樣,只有一條Person (Test2) -- initialize
輸出,我們可以得出第一條結(jié)論:
當(dāng)類第一次接受到消息時(shí),就會調(diào)用
initialize
方法。當(dāng)分類重寫了initialize
方法時(shí),只會調(diào)用分類的initialize
方法(覆蓋本類的initialize
方法)。當(dāng)然,多個(gè)分類存在時(shí),調(diào)用順序同樣跟編譯順序有關(guān),會調(diào)用最后編譯的分類的initialize
方法。
還有一點(diǎn)就是存在子類時(shí),調(diào)用子類的initialize
方法之前,會先調(diào)用父類的initialize
方法,然后再調(diào)用子類的initialize
方法。這一點(diǎn)大家可以嘗試敲代碼測試一下。
下面我們查看一下initialize
方法的調(diào)用源碼:
我們看到,
initialize
方法的調(diào)用是通過消息發(fā)送機(jī)制調(diào)用的,通過isa
指針找到對應(yīng)的方法和實(shí)現(xiàn)。因此會首先找到分類中的initialize
方法實(shí)現(xiàn)優(yōu)先調(diào)用,這也驗(yàn)證了我們上面的先調(diào)用分類的initialize
方法的結(jié)論。
load方法與initialize方法的區(qū)別
- 調(diào)用方式
1.load
是根據(jù)函數(shù)地址直接調(diào)用
2.initialize
是通過objc_msgSend
調(diào)用- 調(diào)用時(shí)刻
1.load
是runtime
加載類、分類的時(shí)候調(diào)用(只會調(diào)用1次)
2.initialize
是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會initialize
一次(父類的initialize
方法可能會被調(diào)用多次)- 調(diào)用順序
load
1.先調(diào)用類的load
a) 先編譯的類,優(yōu)先調(diào)用load
b) 調(diào)用子類的load
之前,會先調(diào)用父類的load
2.再調(diào)用分類的load
a) 先編譯的分類,優(yōu)先調(diào)用load
initialize
1.先初始化父類
2.再初始化子類(可能最終調(diào)用的是父類的initialize
方法)- 是否覆蓋方法
load
分類中的load
方法不會覆蓋本類的load
方法
initialize
分類中的initialize
方法會覆蓋本類的initialize
方法
至此我們就完成了對Category的底層探索,如有疑問,歡迎留言。