Load和Initialize的往死了问是一种怎样的体验
Load 和 Initialize 先加載哪個?
父類和子類以及 Category 的關系?
如果是多個 Category 呢?
Load
開發文檔的直接解讀
加載順序總結
-
所有類和分類的 +load 方法都是在被加入到 runtime 的時候調用
-
父類優先于子類加載(內部通過遞歸的的方式實現)
-
在所有本類加載完畢之后再去加載各個分類
-
同一個類的多個分類加載順序可查看 Target -> Build Phases -> Compile Sources
比如,現有Student 繼承于 Person,Student擁有多個分類,在各個類的 + load 方法進行打印,結果如下
| 1 2 3 4 5 6 7 | 2017-06-26?18:30:59.857400+0800?load[70593:5621604]?Person?==>?Load 2017-06-26?18:30:59.857596+0800?load[70593:5621604]?Student?==>?Load 2017-06-26?18:30:59.857656+0800?load[70593:5621604]?Test2?==>?Load 2017-06-26?18:30:59.857707+0800?load[70593:5621604]?Test1?==>?Load 2017-06-26?18:30:59.857724+0800?load[70593:5621604]?Student?+?load2?==>?Load 2017-06-26?18:30:59.857730+0800?load[70593:5621604]?Student?+?load3?==>?Load 2017-06-26?18:30:59.857736+0800?load[70593:5621604]?Student?+?load1?==>?Load |
查看 Compile Sources,觀察 Student 三個分類的加載順序,與 load 調用順序一致
runtime 源碼閱讀
首先看下 objc-runtime-new.mm 中的 void prepare_load_methods(header_info *hi) 函數,在該函數中準備好 類和分類執行 +load 方法的必要條件,以供接下來的調用
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void?prepare_load_methods(header_info?*hi) { ????size_t?count,?i; ????rwlock_assert_writing(&runtimeLock); ????classref_t?*classlist?= ????????_getObjc2NonlazyClassList(hi,?&count); ????for?(i?=?0;?i?<?count;?i++)?{ ????????schedule_class_load(remapClass(classlist[i])); ????} ????category_t?**categorylist?=?_getObjc2NonlazyCategoryList(hi,?&count); ????for?(i?=?0;?i?<?count;?i++)?{ ????????category_t?*cat?=?categorylist[i]; ????????Class?cls?=?remapClass(cat->cls); ????????if?(!cls)?continue;??//?category?for?ignored?weak-linked?class ????????realizeClass(cls); ????????assert(cls->ISA()->isRealized()); ????????add_category_to_loadable_list(cat); ????} } |
同時,在處理類的時候,也調用了同文件中的另外一個函數 static void schedule_class_load(Class cls)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | static?void?schedule_class_load(Class?cls) { ????if?(!cls)?return; ????assert(cls->isRealized());??//?_read_images?should?realize ????if?(cls->data()->flags?&?RW_LOADED)?return; #?warning?此處使用遞歸的思想實現了先調用父類再調用子類的?+load?方法 ????//?Ensure?superclass-first?ordering ????schedule_class_load(cls->superclass); ????add_class_to_loadable_list(cls); ????cls->setInfo(RW_LOADED); } |
當前所有滿足 +load 方法調用條件的類和分類就被分別存放在全局變量 loadable_classes (load_images 方法調用)和 loadable_categories 中了
準備好類和分類后,接下來就是對它們的 +load 方法進行調用了。打開文件 objc-loadmethod.m ,找到其中的 void call_load_methods(void) 函數。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void?call_load_methods(void) { ????static?BOOL?loading?=?NO; ????BOOL?more_categories; ????recursive_mutex_assert_locked(&loadMethodLock); ????//?Re-entrant?calls?do?nothing;?the?outermost?call?will?finish?the?job. ????if?(loading)?return; ????loading?=?YES; ????void?*pool?=?objc_autoreleasePoolPush(); ????do?{ ????????//?1.?Repeatedly?call?class?+loads?until?there?aren't?any?more ????????while?(loadable_classes_used?>?0)?{ ????????????call_class_loads(); ????????} ????????//?2.?Call?category?+loads?ONCE ????????more_categories?=?call_category_loads(); ????????//?3.?Run?more?+loads?if?there?are?classes?OR?more?untried?categories ????}?while?(loadable_classes_used?>?0??||??more_categories); ????objc_autoreleasePoolPop(pool); ????loading?=?NO; } |
同樣的,這個函數的作用就是調用上一步準備好的類和分類中的 +load 方法,并且確保類優先于分類的順序。我們繼續查看在這個函數中調用的另外兩個關鍵函數 static void call_class_loads(void) 和 static BOOL call_category_loads(void) 。由于這兩個函數的作用大同小異,下面就以篇幅較小的 static void call_class_loads(void) 函數為例進行探討
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | static?void?call_class_loads(void) { ????int?i; ????//?Detach?current?loadable?list. ????struct?loadable_class?*classes?=?loadable_classes; ????int?used?=?loadable_classes_used; ????loadable_classes?=?nil; ????loadable_classes_allocated?=?0; ????loadable_classes_used?=?0; ????//?Call?all?+loads?for?the?detached?list. ????for?(i?=?0;?i?<?used;?i++)?{ ????????Class?cls?=?classes[i].cls; ????????load_method_t?load_method?=?(load_method_t)classes[i].method; ????????if?(!cls)?continue; ????????if?(PrintLoading)?{ ????????????_objc_inform("LOAD:?+[%s?load]\n",?cls->nameForLogging()); ????????} ????????(*load_method)(cls,?SEL_load); ????} ????//?Destroy?the?detached?list. ????if?(classes)?_free_internal(classes); } |
這個函數的作用就是真正負責調用類的 +load 方法了。它從全局變量 loadable_classes 中取出所有可供調用的類,并進行清零操作。
| 1 2 3 4 5 6 | //?指向?用于保存類信息的內存的首地址 loadable_classes?=?nil; //?標識已分配的內存空間大小 loadable_classes_allocated?=?0; //?標識已使用的內存空間大小 loadable_classes_used?=?0; |
load_images -> load_images_nolock -> prepare_load_methods -> schedule_class_load -> add_class_to_loadable_list 的時候會將未加載的類添加到 loadable_classes
調用類和分類的 +load 方法是直接使用函數內存地址的方式 (*load_method)(cls, SEL_load); ,而不是使用發送消息 objc_msgSend 的方式。
調用 load 方法之前,所有的 framework 都已經加載到了運行時中,所以調用 framework 中的方法都是安全的
Initialize
首先看一下蘋果官方文檔對 Initialize 的定義
打開 objc-runtime-new.mm,找到以下函數
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //?當我們給某個類發送消息的時候,runtime會調用這個函數在類中查找相應方法的實現或轉發 #warning?考慮以上特性,說明該類接收到第一條消息之前才會調用?+initialize?方法 IMP?lookUpImpOrForward(Class?cls,?SEL?sel,?id?inst, ???????????????????????bool?initialize,?bool?cache,?bool?resolver) { ????... ????????rwlock_unlock_write(&runtimeLock); ????} ????#?warning?注:當類沒有初始化時,?runtime?會調用?void?_class_initialize(Class?cls)?函數對該類進行初始化 ????if?(initialize??&&??!cls->isInitialized())?{ ????????_class_initialize?(_class_getNonMetaClass(cls,?inst)); ????????//?If?sel?==?initialize,?_class_initialize?will?send?+initialize?and? ????????//?then?the?messenger?will?send?+initialize?again?after?this? ????????//?procedure?finishes.?Of?course,?if?this?is?not?being?called? ????????//?from?the?messenger?then?it?won't?happen.?2778172 ????} ????//?The?lock?is?held?to?make?method-lookup?+?cache-fill?atomic? ????//?with?respect?to?method?addition.?Otherwise,?a?category?could? ????... } |
接下來看看具體的初始化代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | void?_class_initialize(Class?cls) { ????... ????Class?supercls; ????BOOL?reallyInitialize?=?NO; #warning?同樣使用遞歸的思想實現了先調用父類再調用子類的順序 ????//?Make?sure?super?is?done?initializing?BEFORE?beginning?to?initialize?cls. ????//?See?note?about?deadlock?above. ????supercls?=?cls->superclass; ????if?(supercls??&&??!supercls->isInitialized())?{ ????????_class_initialize(supercls); ????} ????//?Try?to?atomically?set?CLS_INITIALIZING. ????monitor_enter(&classInitLock); ????if?(!cls->isInitialized()?&&?!cls->isInitializing())?{ ????????cls->setInitializing(); ????????reallyInitialize?=?YES; ????} ????monitor_exit(&classInitLock); ????if?(reallyInitialize)?{ ????????//?We?successfully?set?the?CLS_INITIALIZING?bit.?Initialize?the?class. ????????//?Record?that?we're?initializing?this?class?so?we?can?message?it. ????????_setThisThreadIsInitializingClass(cls); ????????//?Send?the?+initialize?message. ????????//?Note?that?+initialize?is?sent?to?the?superclass?(again)?if? ????????//?this?class?doesn't?implement?+initialize.?2157218 ????????if?(PrintInitializing)?{ ????????????_objc_inform("INITIALIZE:?calling?+[%s?initialize]", ?????????????????????????cls->nameForLogging()); ????????} #warning?注意這里使用了?objc_msgSend,就意味著該方法就和其他普通方法一樣,子類要沿用父類的方法,分類會會覆蓋本類中的方法。 #warning?同時,如果子類沒有實現但父類實現了該方法,那么父類的該方法就要被實現多次 ????????((void(*)(Class,?SEL))objc_msgSend)(cls,?SEL_initialize); ????????if?(PrintInitializing)?{ ????????????_objc_inform("INITIALIZE:?finished?+[%s?initialize]", ????... } |
-
針對 Category,經測試發現:Category 中 Initialize 方法會覆蓋其本類中的方法(和其他普通方法效果一樣)
-
在 Initialize 方法內部可以進行一些不方便在編譯期進行初始化的靜態變量的賦值
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | #warning?Person.m //?int?等基本類型可以在編譯期進行賦值 static?int?numCount?=?0;? //?對象無法在編譯器進行賦值 static?NSMutableArray?*dataSource; +?(void)initialize?{ ????if?(self?==?[Person?class])?{ ????????//?不能在編譯期賦值的對象在這里進行賦值 ????????dataSource?=?[[NSMutableArray?alloc]?init]; ????} } |
異常情況:在A類的load 方法中調用了B類的類方法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @implementation?Father +?(void)load?{ ????NSLog(@"father==>?load===%@",?[Dog?class]); } +(void)initialize?{ ????NSLog(@"Father===>initialize"); } @end #warning?打印結果如下 2017-08-09?11:19:09.838?tests[34274:8415363]?Dog===>initialize 2017-08-09?11:19:09.839?tests[34274:8415363]?father==>?load===Dog 2017-08-09?11:19:09.839?tests[34274:8415363]?Dog==>?load 2017-08-09?11:19:09.840?tests[34274:8415363]?child==>?load 2017-08-09?11:19:09.840?tests[34274:8415363]?child?+?hahha==>?load 2017-08-09?11:19:09.840?tests[34274:8415363]?main |
以下是Compile Source 截圖
總結
-
正常情況下(即沒有在 load 方法中調用相關類方法),load 和 Initialize 方法都在實例化對象之前調用,load相當于裝載方法,都在main()函數之前調用,Initialize方法都在main() 函數之后調用。
-
如果在A類的 load 方法中調用 B 類的類方法,那么在調用A的Load 方法之前,會先調用一下B類的initialize 方法,但是B類的load 方法還是按照 Compile Source 順序進行加載
-
所有類的 load 方法都會被調用,先調用父類、再調用子類,多個分類會按照Compile Sources 順序加載。但是Initialize 方法會被覆蓋,子類父類分類中只會執行一個
-
load 方法內部一般用來實現 Method Swizzle,Initialize方法一般用來初始化全局變量或者靜態變量
-
兩個方法都不能主動調用,也不需要通過 super 繼承父類方法,但是 Initialize 方法會在子類沒有實現的時候調用父類的該方法,而 load 不會
總結
以上是生活随笔為你收集整理的Load和Initialize的往死了问是一种怎样的体验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 各种基本的排序算法在Object-C实现
- 下一篇: 产品分析报告|读书新贵——《网易蜗牛读书