ObjC load与initialize 简析
+ (void)load
每個類,每個分類中都存在一個+(void)load方法。我們不需要顯式的進(jìn)行調(diào)用,當(dāng)runtime動態(tài)加載類、分類的時候會進(jìn)行調(diào)用。
創(chuàng)建一個command line 項目 創(chuàng)建幾個類。Student繼承自Person,每個類包括其Category中都實現(xiàn)load方法,運行我們發(fā)現(xiàn)控制臺打印了以下信息。
我們發(fā)現(xiàn)類中的load方法沒有被Category中的load方法覆蓋,而是全都進(jìn)行了調(diào)用。這是為什么呢?另外load方法的調(diào)用順序有什么規(guī)律呢?
runtime 源碼-窺探load方法的調(diào)用
通過runtime源碼我們可以看到在runtime的入口函數(shù)void _objc_init(void)(存在于objc_os.mm中)中一段代碼加載images(鏡像)。_dyld_objc_notify_register(&map_images, load_images, unmap_image);
進(jìn)入load_images可以發(fā)現(xiàn)有調(diào)用load方法的函數(shù)
通過函數(shù)名可以知道這里實現(xiàn)對load方法展開調(diào)用。查看該方法的實現(xiàn),我們發(fā)現(xiàn)它又是通過call_class_loads對類的load方法進(jìn)行調(diào)用,通過call_category_loads對category中的load方法進(jìn)行調(diào)用的。它使用了一個while循環(huán)首先調(diào)用類的load方法,類的load方法全部調(diào)用完成之后,再調(diào)用Category的load方法。
查看call_class_load函數(shù)的實現(xiàn),我們發(fā)現(xiàn)它通過遍歷一個數(shù)組獲取一個結(jié)構(gòu)體loadable_class。
這里的method就是load函數(shù)的地址,而call_class_loads獲取函數(shù)地址,直接進(jìn)行調(diào)用。call_category_loads也是如此。
那么loadable_classes這個list是按照什么規(guī)律生成的呢?
重新觀察load_iamges函數(shù),我們發(fā)現(xiàn)有一個prepare_load_methods函數(shù)
這個方法遍歷classlist,通過schedule_class_load函數(shù)準(zhǔn)備class的load方法。這個函數(shù)中有遞歸調(diào)用了自己,首先處理父類,最后調(diào)用了add_class_to_loadable_list函數(shù),將load生成loadable_classes數(shù)組和loadable_classes_used可調(diào)用的load方法的數(shù)量。
通過這個函數(shù)的實現(xiàn),我們也可以驗證上文中所說的loadable_class中的method就是load方法,我們可以看到method是通過調(diào)用getLoadMethod獲取并添加到結(jié)構(gòu)體loadable_class中的。
Category的將load方法加載到數(shù)組的方法跟class基本一致,但是Category是按照編譯順序進(jìn)行添加的。
+ (void)load的一些問題
綜上所述:
- +(void)load方法是在runtime加載類或分類的時候調(diào)用的。
- 每個類分類的load在程序運行過程中只會調(diào)用一次。調(diào)用的完成loadable_classes_used數(shù)量會置為0。
- 先調(diào)用類的load方法(先編譯的先調(diào)用)父類load優(yōu)先調(diào)用
- 分類的load方法 先編譯的先調(diào)用
- load方法是直接查找到函數(shù)地址進(jìn)行調(diào)用的而不是通過消息發(fā)送機(jī)制調(diào)用的,所以不會出現(xiàn)方法覆蓋
+(void)initialize
initialize在一個類剛剛接收到消息的時候進(jìn)行調(diào)用。利用上面的代碼,實現(xiàn)initialize方法,在main函數(shù)中讓student給alloc發(fā)消息。得到以下打印:
經(jīng)過上面的經(jīng)驗可以得知,initialize是通過objc_msgSend方法調(diào)用的,所以只打印了category的initialize中的日志。而category的調(diào)用,根據(jù)Category的底層實現(xiàn)可以得知,后編譯的被調(diào)用了。
runtime 源碼-窺探initialize方法的調(diào)用
由于objc_msgSend的實現(xiàn)沒有開源,所以通過查看objc-runtime-new.mm中的獲取實例方法的函數(shù)(class_getInstanceMethod)進(jìn)行窺探。
在該方法中通過調(diào)用lookUpImpOrNil方法查找方法的實現(xiàn),這個方法又調(diào)用了lookUpImpOrForward方法繼續(xù)查找。
在lookUpImpOrForward方法中如果存在initialize并且沒有調(diào)用過,則通過_class_initialize (_class_getNonMetaClass(cls, inst));調(diào)用initialize。
_class_initialize (_class_getNonMetaClass(cls, inst));中通過遞歸先通過callInitialize(cls);調(diào)用父類的initialize。
callInitialize(cls);中就可以看出是通過objc_msgSend調(diào)用的。
所以會執(zhí)行通過isa指針查找方法實現(xiàn)的過程。
+(void)initialize的一些問題
- +(void)initialize是通過objc_msgSend調(diào)用的
- category實現(xiàn)了initialize方法會覆蓋父類的initilize方法
- initialize也只初始化一次,但是由于是通過objc_msgSend的調(diào)用的,如果子類沒有實現(xiàn)initialize,而父類實現(xiàn)了,則子類每次初始化的時候都會調(diào)用父類的+(void)initialize,所以父類的+(void)initialize方法會被調(diào)用多次。
總結(jié)
以上是生活随笔為你收集整理的ObjC load与initialize 简析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 02 面向对象之:类空间问题以及类之间的
- 下一篇: 点量OTT TV 点播软件模式为何受海外