前言
最近在群里看到有人提問:兩個類A,B,為什么在類A的方法里可以創建B對象,調用B類對象的方法,不是說load方法是在類被加載時調用的方法嗎。那么B的load方法沒調用前類B不是沒被加載嗎,怎么能調用。
當然這里是提問者本身對load方法調用時機的誤解,當時我對這塊理解也不是很到位,平時對于runtime也只是一般的使用:動態綁定屬性,Swizzle方法實現等,所以僅僅是引用了官方的對load方法描述來回答這個問題,“在load方法中你可以調用其他類的方法,但是并不代表該類的load方法被執行過”。
回家后問題也一直掛在心上,load方法到底是何時被調用的,Class又是何時被加載的呢,他們之間的順序又是什么呢?
從runtime初始化開始
一切都要從runtime初始化開始,_objc_init方法是runtime初始化的方法,可在開源代碼中查看
其中,最后兩行注冊了兩個通知,分別交給map_images,load_image方法來處理。
map_images 主要是在image加載進內容后對其二進制內容進行解析,初始化里面的類的結構等。
load_images 主要是調用call_load_methods。按照繼承層次依次調用Class的+load方法然后再是Category的+load方法。
在demo里map_images和load_image方法中都打下斷點
這里就只列舉map_images方法斷點截圖
運行后名為"debug-objc"的Target
發現:項目中的對debug-objc鏡像文件(tagert編譯運行后的可執行文件)來說,系統在初始化runtime后,main函數啟動前對其進行bind,此時會調用map_images方法對其進行類結構初始化等處理,然后再調用在load_image方法調用所有的load方法(下載demo運行試試),所以在runtime調用項目中所有的load方法時,我們所有的類的結構已經加載進內存并初始化了,此時在load方法中可以創建任何類的實例并給他們發送消息。
我們再在_objc_init 里打個斷點:
發現調用棧是由_dyld_start開始的,這里引申到dyld(Apple動態鏈接器),在程序啟動后當系統做好了一些初始化的準備,會將后面的事甩鍋給dyld負責,dyld會將程序依賴動態庫加載進內存初始化,但他干不來所有事,所以他就初始化了runtime小弟來幫他將所有即將使用的庫的二進制數據進行解析并初始化里面類的結構,關于dyld更多介紹可見相關資料以及dyld開源源碼。
總結:
runtime 調用項目中所有的load方法時,所有的類的結構已經初始化了,此時在load方法中可以使用任何類創建實例并給他們發送消息。
Tips:
1.由于load方法在main函數前被調用,所以在load方法中盡量避免費時的操作,因為這些操作都將導致程序啟動時間的延長。
2.runtime中call_load_methods里是通過load方法的地址直接調用的load方法,而不是通過消息機制來調用的。可見源碼:
static void call_class_loads(void)
{
//調用所有類的load方法
....
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
...
(*load_method)(cls, SEL_load);
}
...
}
這就是為什么分類中的load方法并不會覆蓋主類以及其他同主類的分類里的load 方法實現了。
結尾
客官,點個贊再走唄,哦,對了,記得帶(guan)上(zhu)門(wo)!_