Objective-C的+load方法調(diào)用原理分析
Objective-C之Category的底層實現(xiàn)原理
Objective-C為我們提供了兩種方法去運行對類進(jìn)行相關(guān)設(shè)置的代碼。
-
+load
:該方法會在很早階段(同時也是比較危險的階段,可能導(dǎo)致崩潰)被調(diào)用,一旦某個類被Runtime加載,該類的+load
方法就會被調(diào)用。我們可以在這個方法里面寫一些必須要在程序運行非常早期階段就需要運行的代碼。 -
+initialize
:該方法可以比較安全的處理大部分情況下的設(shè)置任務(wù)代碼,因為會在一個更加安全的環(huán)境下被調(diào)用。你幾乎可以在這個方法里面做任何事情,除非,你的代碼
需要等到外部實體向這個類發(fā)消息之后,才能運行,那么將你的代碼
放在+initialize
方法里面將是不合適的。
關(guān)于+initialize方法的一些結(jié)論
-
+initialize
方法會在類第一次接收到消息的時候調(diào)用 -
+initialize
方法是通過objc_msgSend()
進(jìn)行調(diào)用的
分析
+initialize
既然是在類對象第一次接受消息的時候調(diào)用,我們知道接受消息整個邏輯的底層其實就是通過objc_msgSend(Class cls, SEL sel)
函數(shù)開始的,而該函數(shù)主要思路就是首先通過isa
先進(jìn)行方法的查找,找到后就進(jìn)行方法調(diào)用。所以系統(tǒng)對+initialize
的調(diào)用,就可能發(fā)生在上述的兩個步驟之中。
擼一波源碼
那么我們來看看這個函數(shù)的源碼,通過關(guān)鍵字objc_msgSend(
來搜索一下,結(jié)果如下圖
可以看到能在源碼里面找到的相關(guān)文件都是一堆.s文件,也就是匯編文件,說明源碼提供了該函數(shù)的匯編實現(xiàn),這是一種半開源形式,想要讀懂,就需要匯編基礎(chǔ)。令人悲傷的是,此時此刻碼字的我,還不會匯編,自然也就看不懂。
不過發(fā)現(xiàn)注釋里面有一句代碼,是跟方法查詢相關(guān)的函數(shù),
objc_msgLookup(id self, SEL _cmd, ...)
,那么繼續(xù)查看一下,萬一是看得懂的C函數(shù)呢,走起看來這個方向是暫時走不通了。還好,我從大佬MJ老師那里,了解到一個跟
objc_msgLookup
等價的方法,它就是Method class_getInstanceMethod(Class cls, SEL sel)
。進(jìn)入該方法查看一下objc-runtime-new.mm
文件下(很明顯objc-runtime-old.mm
應(yīng)該是過時的源碼)看到該函數(shù)的實現(xiàn)里面,有一個lookUpImpOrNil
函數(shù),這個便是具體的方法查找函數(shù),繼續(xù)進(jìn)入其中lookUpImpOrForward
if (需要初始化 && class還沒進(jìn)行過初始化) {
對class進(jìn)行初始化
}
???注意,上面這段為代碼邏輯,是發(fā)生在方法查找過程的,也就是說,類對象每次接收到消息,進(jìn)行方法查找的時候,都會進(jìn)入這段邏輯,很明顯,該邏輯中,if判斷條件就確保了,對于類對象的初始化操作只會進(jìn)行一次,并且發(fā)生在類對象第一次接收到消息的時候。
那么看看對類對象進(jìn)行初始化的具體過程,也就是_class_initialize
函數(shù),進(jìn)入
針對我們研究的問題,我們找到關(guān)鍵部分代碼,該函數(shù)里面,先判斷了父類是否被initialized,如果沒有的話,遞歸調(diào)用本函數(shù)對父類進(jìn)行處理,完畢之后,在通過
callInitialize()
對+initialize
進(jìn)行實際調(diào)用。
而callInitialize()
的實現(xiàn),也證實了,系統(tǒng)確實是通過消息機制objc_msgSend()
來調(diào)用+initialize
方法的。好了,源代碼分析結(jié)束。
上機調(diào)試
場景一
CLPerson的+initialize方法
小結(jié)(一):該場景證明了,
+initialize
方法的調(diào)用發(fā)生在類對象第一次接受消息的時候。
場景二
分類的編譯順序
CLPerson+Cate01的+initialize方法
CLPerson+Cate02的+initialize方法
小結(jié)(二):該場景證明了,系統(tǒng)對
+initialize
方法的調(diào)用是通過消息機制,也就是objc_msgSend
函數(shù)來發(fā)起的,根據(jù)我的Objective-C之Category的底層實現(xiàn)原理一文對Category
的“方法覆蓋”現(xiàn)象的研究,也是支持該場景下的最后日志打印結(jié)果:打印的是--CLPerson+Cate02
的+initialize
方法--。
場景三
CLStudent繼承自CLPerson
CLStudent的+initialize方法
CLTeacher繼承自CLPerson
CLTeacher的+initialize方法
小結(jié)(三):該場景證明了我們從源碼中發(fā)現(xiàn)的邏輯:在+initialize方法都實現(xiàn)了的前提下,系統(tǒng)對一個類對象調(diào)用
+initialize
方法的之前,會先調(diào)用其父類的+initialize
方法(?要求父類的+initialize
方法必須從來沒有被調(diào)用過)
場景四
接著上面的場景三,我們對CLStudent和CLTeacher的進(jìn)行微調(diào),不實現(xiàn)他們的+initialize
注銷CLStudent的+initialize實現(xiàn)
注銷CLTeacher的+initialize實現(xiàn)
小結(jié)(四):從結(jié)果看,
CLPerson
的+initialize
方法被調(diào)用了三次。
- 第(1)次調(diào)用,是CLStudent首次接受消息時,系統(tǒng)對父類CLPerson進(jìn)行的
+initialize
調(diào)用,也就是objc_msgSend([CLPerson class] ,@selector(initialize))
- 第(2)次調(diào)用,是CLStudent首次接受消息時,系統(tǒng)對CLStudent進(jìn)行的
+initialize
調(diào)用,也就是objc_msgSend([CLStudent class],@selector(initialize))
,因為CLStudent沒有實現(xiàn)自己+initialize
方法,所以根據(jù)消息機制的原理,調(diào)用了父類CLPerson的+inilialize
方法。- 第(3)次調(diào)用,是CLTeacher首次接受消息時,系統(tǒng)對CLTeacher進(jìn)行的
+initialize
調(diào)用,也就是objc_msgSend([CLTeacher class],@selector(initialize))
,因為CLTeacher沒有實現(xiàn)自己+initialize
方法,所以根據(jù)消息機制的原理,調(diào)用了父類CLPerson的+inilialize
方法。
總結(jié)
-
+initialize
方法會在類對象 第一次 接收到消息的時候調(diào)用 - 調(diào)用順序:調(diào)用某個類的
+initialize
之前,會先調(diào)用其父類的+initialize
(前提是父類的+initialize
從來沒有被調(diào)用過) - 由于
+initialize
的調(diào)用,是通過消息機制,也就是objc_msgSend()
,因此如果子類的+initialize
沒有實現(xiàn),就會去調(diào)用父類的+initialize
- 基于同樣的原因,如果分類實現(xiàn)的
+initialize
,那么就會“覆蓋”類對象本身的+initialize
方法而被調(diào)用。