Objective-C的+load方法調用原理分析
Objective-C之Category的底層實作原理
Objective-C為我們提供了兩種方法去運作對類進行相關設定的代碼。
-
:該方法會在很早階段(同時也是比較危險的階段,可能導緻崩潰)被調用,一旦某個類被Runtime加載,該類的+load
方法就會被調用。我們可以在這個方法裡面寫一些必須要在程式運作非常早期階段就需要運作的代碼。+load
-
:該方法可以比較安全的處理大部分情況下的設定任務代碼,因為會在一個更加安全的環境下被調用。你幾乎可以在這個方法裡面做任何事情,除非,+initialize
需要等到外部實體向這個類發消息之後,才能運作,那麼将你的代碼
放在你的代碼
方法裡面将是不合适的。+initialize
關于+initialize方法的一些結論
-
方法會在類第一次接收到消息的時候調用+initialize
-
方法是通過+initialize
進行調用的objc_msgSend()
分析
+initialize
既然是在類對象第一次接受消息的時候調用,我們知道接受消息整個邏輯的底層其實就是通過
objc_msgSend(Class cls, SEL sel)
函數開始的,而該函數主要思路就是首先通過
isa
先進行方法的查找,找到後就進行方法調用。是以系統對
+initialize
的調用,就可能發生在上述的兩個步驟之中。
撸一波源碼
那麼我們來看看這個函數的源碼,通過關鍵字
objc_msgSend(
來搜尋一下,結果如下圖
可以看到能在源碼裡面找到的相關檔案都是一堆.s檔案,也就是彙編檔案,說明源碼提供了該函數的彙編實作,這是一種半開源形式,想要讀懂,就需要彙編基礎。令人悲傷的是,此時此刻碼字的我,還不會彙編,自然也就看不懂。
随便點開一個,比如說arm64.s的檔案,看到如此晦澀的彙編代碼,我真的有心無力,隻能默默鼓勵自己:學海無涯,前路漫漫~~
不過發現注釋裡面有一句代碼,是跟方法查詢相關的函數,
objc_msgLookup(id self, SEL _cmd, ...)
,那麼繼續檢視一下,萬一是看得懂的C函數呢,走起
結果還是令人失望,除了一個系統的函數定義,沒有找到相關的實作。
看來這個方向是暫時走不通了。還好,我從大佬MJ老師那裡,了解到一個跟
objc_msgLookup
等價的方法,它就是
Method class_getInstanceMethod(Class cls, SEL sel)
。進入該方法檢視一下
我們在
objc-runtime-new.mm
檔案下(很明顯
objc-runtime-old.mm
應該是過時的源碼)看到該函數的實作裡面,有一個
lookUpImpOrNil
函數,這個便是具體的方法查找函數,繼續進入其中
裡面又包了一層,話不多說,繼續進入函數
lookUpImpOrForward
終于我們發現了想要的東西,該函數裡面,可以開到在一開始,就有一段與類對象初始化相關的邏輯,如上圖紅框,我把它轉成僞代碼的形式便于了解
if (需要初始化 && class還沒進行過初始化) {
對class進行初始化
}
️️️注意,上面這段為代碼邏輯,是發生在方法查找過程的,也就是說,類對象每次接收到消息,進行方法查找的時候,都會進入這段邏輯,很明顯,該邏輯中,if判斷條件就確定了,對于類對象的初始化操作隻會進行一次,并且發生在類對象第一次接收到消息的時候。
那麼看看對類對象進行初始化的具體過程,也就是
_class_initialize
函數,進入
針對我們研究的問題,我們找到關鍵部分代碼,該函數裡面,先判斷了父類是否被initialized,如果沒有的話,遞歸調用本函數對父類進行處理,完畢之後,在通過
callInitialize()
對
+initialize
進行實際調用。
而的實作,也證明了,系統确實是通過消息機制
callInitialize()
來調用
objc_msgSend()
方法的。好了,源代碼分析結束。
+initialize
上機調試
場景一小結(一):該場景證明了,
方法的調用發生在類對象第一次接受消息的時候。
+initialize
場景二小結(二):該場景證明了,系統對
方法的調用是通過消息機制,也就是
+initialize
函數來發起的,根據我的Objective-C之Category的底層實作原理一文對
objc_msgSend
的“方法覆寫”現象的研究,也是支援該場景下的最後日志列印結果:列印的是–
Category
的
CLPerson+Cate02
方法–。
+initialize
場景三小結(三):該場景證明了我們從源碼中發現的邏輯:在+initialize方法都實作了的前提下,系統對一個類對象調用
方法的之前,會先調用其父類的
+initialize
方法(️要求父類的
+initialize
方法必須從來沒有被調用過)
+initialize
場景四
接着上面的場景三,我們對
和
CLStudent
的進行微調,不實作他們的
CLTeacher
+initialize
小結(四):從結果看,
的
CLPerson
方法被調用了三次。
+initialize
- 第(1)次調用,是CLStudent首次接受消息時,系統對父類CLPerson進行的
調用,也就是
+initialize
objc_msgSend([CLPerson class] ,@selector(initialize))
- 第(2)次調用,是CLStudent首次接受消息時,系統對CLStudent進行的
調用,也就是
+initialize
,因為CLStudent沒有實作自己
objc_msgSend([CLStudent class],@selector(initialize))
方法,是以根據消息機制的原理,調用了父類CLPerson的
+initialize
方法。
+inilialize
- 第(3)次調用,是CLTeacher首次接受消息時,系統對CLTeacher進行的
調用,也就是
+initialize
,因為CLTeacher沒有實作自己
objc_msgSend([CLTeacher class],@selector(initialize))
方法,是以根據消息機制的原理,調用了父類CLPerson的
+initialize
方法。
+inilialize
總結
-
方法會在類對象 第一次 接收到消息的時候調用+initialize
- 調用順序:調用某個類的
之前,會先調用其父類的+initialize
(前提是父類的+initialize
從來沒有被調用過)+initialize
- 由于
的調用,是通過消息機制,也就是+initialize
,是以如果子類的objc_msgSend()
沒有實作,就會去調用父類的+initialize
+initialize
- 基于同樣的原因,如果分類實作的
,那麼就會“覆寫”類對象本身的+initialize
方法而被調用。+initialize
好了,關于initialize的調用原理分析,就到這裡結束了,各位看官有空常來,慢走不送~~
Objective-C的+load方法調用原理分析
Objective-C之Category的底層實作原理