天天看點

Objective-C的+initialize方法調用原理分析

Objective-C的+load方法調用原理分析

Objective-C之Category的底層實作原理

Objective-C為我們提供了兩種方法去運作對類進行相關設定的代碼。

  • +load

    :該方法會在很早階段(同時也是比較危險的階段,可能導緻崩潰)被調用,一旦某個類被Runtime加載,該類的

    +load

    方法就會被調用。我們可以在這個方法裡面寫一些必須要在程式運作非常早期階段就需要運作的代碼。
  • +initialize

    :該方法可以比較安全的處理大部分情況下的設定任務代碼,因為會在一個更加安全的環境下被調用。你幾乎可以在這個方法裡面做任何事情,除非,

    你的代碼

    需要等到外部實體向這個類發消息之後,才能運作,那麼将

    你的代碼

    放在

    +initialize

    方法裡面将是不合适的。
關于+initialize方法的一些結論
  • +initialize

    方法會在類第一次接收到消息的時候調用
  • +initialize

    方法是通過

    objc_msgSend()

    進行調用的
分析
Objective-C的+initialize方法調用原理分析

+initialize

既然是在類對象第一次接受消息的時候調用,我們知道接受消息整個邏輯的底層其實就是通過

objc_msgSend(Class cls, SEL sel)

函數開始的,而該函數主要思路就是首先通過

isa

先進行方法的查找,找到後就進行方法調用。是以系統對

+initialize

的調用,就可能發生在上述的兩個步驟之中。

撸一波源碼

那麼我們來看看這個函數的源碼,通過關鍵字

objc_msgSend(

來搜尋一下,結果如下圖

Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析

可以看到能在源碼裡面找到的相關檔案都是一堆.s檔案,也就是彙編檔案,說明源碼提供了該函數的彙編實作,這是一種半開源形式,想要讀懂,就需要彙編基礎。令人悲傷的是,此時此刻碼字的我,還不會彙編,自然也就看不懂。

Objective-C的+initialize方法調用原理分析

随便點開一個,比如說arm64.s的檔案,看到如此晦澀的彙編代碼,我真的有心無力,隻能默默鼓勵自己:學海無涯,前路漫漫~~

不過發現注釋裡面有一句代碼,是跟方法查詢相關的函數,

objc_msgLookup(id self, SEL _cmd, ...)

,那麼繼續檢視一下,萬一是看得懂的C函數呢,走起

Objective-C的+initialize方法調用原理分析

結果還是令人失望,除了一個系統的函數定義,沒有找到相關的實作。

看來這個方向是暫時走不通了。還好,我從大佬MJ老師那裡,了解到一個跟

objc_msgLookup

等價的方法,它就是

Method class_getInstanceMethod(Class cls, SEL sel)

。進入該方法檢視一下

Objective-C的+initialize方法調用原理分析

我們在

objc-runtime-new.mm

檔案下(很明顯

objc-runtime-old.mm

應該是過時的源碼)看到該函數的實作裡面,有一個

lookUpImpOrNil

函數,這個便是具體的方法查找函數,繼續進入其中

Objective-C的+initialize方法調用原理分析

裡面又包了一層,話不多說,繼續進入函數

lookUpImpOrForward

Objective-C的+initialize方法調用原理分析

終于我們發現了想要的東西,該函數裡面,可以開到在一開始,就有一段與類對象初始化相關的邏輯,如上圖紅框,我把它轉成僞代碼的形式便于了解

if (需要初始化  &&  class還沒進行過初始化) {
        對class進行初始化
    }
           
️️️注意,上面這段為代碼邏輯,是發生在方法查找過程的,也就是說,類對象每次接收到消息,進行方法查找的時候,都會進入這段邏輯,很明顯,該邏輯中,if判斷條件就確定了,對于類對象的初始化操作隻會進行一次,并且發生在類對象第一次接收到消息的時候。

那麼看看對類對象進行初始化的具體過程,也就是

_class_initialize

函數,進入

Objective-C的+initialize方法調用原理分析

針對我們研究的問題,我們找到關鍵部分代碼,該函數裡面,先判斷了父類是否被initialized,如果沒有的話,遞歸調用本函數對父類進行處理,完畢之後,在通過

callInitialize()

+initialize

進行實際調用。

Objective-C的+initialize方法調用原理分析

callInitialize()

的實作,也證明了,系統确實是通過消息機制

objc_msgSend()

來調用

+initialize

方法的。好了,源代碼分析結束。
上機調試
場景一
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
小結(一):該場景證明了,

+initialize

方法的調用發生在類對象第一次接受消息的時候。
場景二
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
小結(二):該場景證明了,系統對

+initialize

方法的調用是通過消息機制,也就是

objc_msgSend

函數來發起的,根據我的Objective-C之Category的底層實作原理一文對

Category

的“方法覆寫”現象的研究,也是支援該場景下的最後日志列印結果:列印的是–

CLPerson+Cate02

+initialize

方法–。
場景三
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
小結(三):該場景證明了我們從源碼中發現的邏輯:在+initialize方法都實作了的前提下,系統對一個類對象調用

+initialize

方法的之前,會先調用其父類的

+initialize

方法(️要求父類的

+initialize

方法必須從來沒有被調用過)

場景四

接着上面的場景三,我們對

CLStudent

CLTeacher

的進行微調,不實作他們的

+initialize

Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
Objective-C的+initialize方法調用原理分析
小結(四):從結果看,

CLPerson

+initialize

方法被調用了三次。
  • 第(1)次調用,是CLStudent首次接受消息時,系統對父類CLPerson進行的

    +initialize

    調用,也就是

    objc_msgSend([CLPerson class] ,@selector(initialize))

  • 第(2)次調用,是CLStudent首次接受消息時,系統對CLStudent進行的

    +initialize

    調用,也就是

    objc_msgSend([CLStudent class],@selector(initialize))

    ,因為CLStudent沒有實作自己

    +initialize

    方法,是以根據消息機制的原理,調用了父類CLPerson的

    +inilialize

    方法。
  • 第(3)次調用,是CLTeacher首次接受消息時,系統對CLTeacher進行的

    +initialize

    調用,也就是

    objc_msgSend([CLTeacher class],@selector(initialize))

    ,因為CLTeacher沒有實作自己

    +initialize

    方法,是以根據消息機制的原理,調用了父類CLPerson的

    +inilialize

    方法。

總結

  • +initialize

    方法會在類對象 第一次 接收到消息的時候調用
  • 調用順序:調用某個類的

    +initialize

    之前,會先調用其父類的

    +initialize

    (前提是父類的

    +initialize

    從來沒有被調用過)
  • 由于

    +initialize

    的調用,是通過消息機制,也就是

    objc_msgSend()

    ,是以如果子類的

    +initialize

    沒有實作,就會去調用父類的

    +initialize

  • 基于同樣的原因,如果分類實作的

    +initialize

    ,那麼就會“覆寫”類對象本身的

    +initialize

    方法而被調用。
好了,關于initialize的調用原理分析,就到這裡結束了,各位看官有空常來,慢走不送~~

Objective-C的+load方法調用原理分析

Objective-C之Category的底層實作原理