Objective-C的+initialize方法調(diào)用原理分析

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)用可能的發(fā)生的地方

+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é)果如下圖

objc_msgSend的匯編實現(xiàn)

可以看到能在源碼里面找到的相關(guān)文件都是一堆.s文件,也就是匯編文件,說明源碼提供了該函數(shù)的匯編實現(xiàn),這是一種半開源形式,想要讀懂,就需要匯編基礎(chǔ)。令人悲傷的是,此時此刻碼字的我,還不會匯編,自然也就看不懂。

image.png
隨便點開一個,比如說arm64.s的文件,看到如此晦澀的匯編代碼,我真的有心無力,只能默默鼓勵自己:學(xué)海無涯,前路漫漫~~
不過發(fā)現(xiàn)注釋里面有一句代碼,是跟方法查詢相關(guān)的函數(shù),objc_msgLookup(id self, SEL _cmd, ...),那么繼續(xù)查看一下,萬一是看得懂的C函數(shù)呢,走起
結(jié)果還是令人失望,除了一個系統(tǒng)的函數(shù)定義,沒有找到相關(guān)的實現(xiàn)。
看來這個方向是暫時走不通了。還好,我從大佬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)入其中
里面又包了一層,話不多說,繼續(xù)進(jìn)入函數(shù)lookUpImpOrForward
終于我們發(fā)現(xiàn)了想要的東西,該函數(shù)里面,可以開到在一開始,就有一段與類對象初始化相關(guān)的邏輯,如上圖紅框,我把它轉(zhuǎn)成偽代碼的形式便于理解

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)用。

好了,關(guān)于initialize的調(diào)用原理分析,就到這里結(jié)束了,各位看官有空常來

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。