ObjC load與initialize 簡析

SoC發表於2019-01-24

+ (void)load

每個類,每個分類中都存在一個+(void)load方法。我們不需要顯式的進行呼叫,當runtime動態載入類、分類的時候會進行呼叫。

例項檔案目錄

建立一個command line 專案 建立幾個類。Student繼承自Person,每個類包括其Category中都實現load方法,執行我們發現控制檯列印了以下資訊。

load資訊

我們發現類中的load方法沒有被Category中的load方法覆蓋,而是全都進行了呼叫。這是為什麼呢?另外load方法的呼叫順序有什麼規律呢?

runtime 原始碼-窺探load方法的呼叫

通過runtime原始碼我們可以看到在runtime的入口函式void _objc_init(void)(存在於objc_os.mm中)中一段程式碼載入images(映象)。_dyld_objc_notify_register(&map_images, load_images, unmap_image);

進入load_images可以發現有呼叫load方法的函式

load_images

通過函式名可以知道這裡實現對load方法展開呼叫。檢視該方法的實現,我們發現它又是通過call_class_loads對類的load方法進行呼叫,通過call_category_loads對category中的load方法進行呼叫的。它使用了一個while迴圈首先呼叫類的load方法,類的load方法全部呼叫完成之後,再呼叫Category的load方法。

檢視call_class_load函式的實現,我們發現它通過遍歷一個陣列獲取一個結構體loadable_class。

loadable_class

這裡的method就是load函式的地址,而call_class_loads獲取函式地址,直接進行呼叫。call_category_loads也是如此。

那麼loadable_classes這個list是按照什麼規律生成的呢?

重新觀察load_iamges函式,我們發現有一個prepare_load_methods函式

prepare_load_methods

這個方法遍歷classlist,通過schedule_class_load函式準備class的load方法。這個函式中有遞迴呼叫了自己,首先處理父類,最後呼叫了add_class_to_loadable_list函式,將load生成loadable_classes陣列和loadable_classes_used可呼叫的load方法的數量。

add_class_to_loadable_list

通過這個函式的實現,我們也可以驗證上文中所說的loadable_class中的method就是load方法,我們可以看到method是通過呼叫getLoadMethod獲取並新增到結構體loadable_class中的。

Category的將load方法載入到陣列的方法跟class基本一致,但是Category是按照編譯順序進行新增的。

+ (void)load的一些問題

綜上所述:

  • +(void)load方法是在runtime載入類或分類的時候呼叫的。
  • 每個類分類的load在程式執行過程中只會呼叫一次。呼叫的完成loadable_classes_used數量會置為0。
  • 先呼叫類的load方法(先編譯的先呼叫)父類load優先呼叫
  • 分類的load方法 先編譯的先呼叫
  • load方法是直接查詢到函式地址進行呼叫的而不是通過訊息傳送機制呼叫的,所以不會出現方法覆蓋

+(void)initialize

initialize在一個類剛剛接收到訊息的時候進行呼叫。利用上面的程式碼,實現initialize方法,在main函式中讓student給alloc發訊息。得到以下列印:

initialize資訊

經過上面的經驗可以得知,initialize是通過objc_msgSend方法呼叫的,所以只列印了category的initialize中的日誌。而category的呼叫,根據Category的底層實現可以得知,後編譯的被呼叫了。

runtime 原始碼-窺探initialize方法的呼叫

由於objc_msgSend的實現沒有開源,所以通過檢視objc-runtime-new.mm中的獲取例項方法的函式(class_getInstanceMethod)進行窺探。

在該方法中通過呼叫lookUpImpOrNil方法查詢方法的實現,這個方法又呼叫了lookUpImpOrForward方法繼續查詢。

lookUpImpOrForward方法中如果存在initialize並且沒有呼叫過,則通過_class_initialize (_class_getNonMetaClass(cls, inst));呼叫initialize。

_class_initialize

_class_initialize (_class_getNonMetaClass(cls, inst));中通過遞迴先通過callInitialize(cls);呼叫父類的initialize。

callInitialize(cls);中就可以看出是通過objc_msgSend呼叫的。

呼叫objc_msgSend

所以會執行通過isa指標查詢方法實現的過程。

+(void)initialize的一些問題

  • +(void)initialize是通過objc_msgSend呼叫的
  • category實現了initialize方法會覆蓋父類的initilize方法
  • initialize也只初始化一次,但是由於是通過objc_msgSend的呼叫的,如果子類沒有實現initialize,而父類實現了,則子類每次初始化的時候都會呼叫父類的+(void)initialize,所以父類的+(void)initialize方法會被呼叫多次。

相關文章