先了解一下應用啟動之後,做了什麼。
main.m 中的 main() 是程式的入口,但在進入 main 函式之前,程式就執行了很多程式碼(不然也不會啟動那麼久)。
啟動後執行順序:
將程式依賴的動態連結庫載入進記憶體
載入可執行檔案中的所有符號、程式碼
runtime 解析被編譯過的符號程式碼,遍歷所有 Class,按繼承層級依次呼叫Class 的 load 方法和其 Category 的 load 方法。
load 方法的執行順序
首先來做點測試,來看看 load 方法的執行順序。
先建一個 Single View Application。展開 Build Phases 的 Compile Sources,如下圖:
在每個類的 @implementation 里加上
1 2 3 |
+ (void)load { NSLog(@"%s", __func__); } |
來看看每個類的 load 方法的呼叫順序(main.m 我做了特殊處理)。以下是執行結果:
1 2 3 |
JMTestDemo[14939:1769791] +[ViewController load] JMTestDemo[14939:1769791] +[AppDelegate load] JMTestDemo[14939:1769791] +[ClassMain load] |
順序和 Compile Sources 順序一致,目前來看,Compile Sources 的順序就是 load 方法的呼叫順序。
再來一個測試,依次新增 ClassFather,以及它的子類 ClssSon,以及另一個 ClassA。結果 Compile Sources 的順序和我想象中的不一樣,如下圖
看起來毫無規律啊。(這是一個問題,先記錄一下)
這個時候我改變順序,將 ClassSon 移動到最前面,ClassFather 移動到最後面。如圖
下面是執行結果:
1 2 3 4 5 6 |
JMTestDemo[15034:1788736] +[ClassFather load] JMTestDemo[15034:1788736] +[ClassSon load] JMTestDemo[15034:1788736] +[ViewController load] JMTestDemo[15034:1788736] +[AppDelegate load] JMTestDemo[15034:1788736] +[ClassA load] JMTestDemo[15034:1788736] +[ClassMain load] |
也就是本應先執行 ClassSon load 方法,但先執行了 ClassFather load 方法,再來一次測試。
我將 ClassSon 裡的 load 方法註釋掉,以下是執行結果:
1 2 3 4 5 |
JMTestDemo[15055:1791953] +[ViewController load] JMTestDemo[15055:1791953] +[AppDelegate load] JMTestDemo[15055:1791953] +[ClassA load] JMTestDemo[15055:1791953] +[ClassMain load] JMTestDemo[15055:1791953] +[ClassFather load] |
也就是說,只有在你重寫了 load 方法的時候,會在執行你的 load 之前,當父類未載入時,先執行父類的 load 方法。
再來一個分類的情況,分類就很有意思了。先新增了 ClssSon+category,ClssSon+category2,ClassFather+category。將它們移動至最上方。如圖
執行結果
1 2 3 4 5 6 7 8 9 |
JMTestDemo[15181:1808284] +[ClassFather load] JMTestDemo[15181:1808284] +[ClassSon load] JMTestDemo[15181:1808284] +[ViewController load] JMTestDemo[15181:1808284] +[AppDelegate load] JMTestDemo[15181:1808284] +[ClassA load] JMTestDemo[15181:1808284] +[ClassMain load] JMTestDemo[15181:1808284] +[ClassSon(category2) load] JMTestDemo[15181:1808284] +[ClassFather(category) load] JMTestDemo[15181:1808284] +[ClassSon(category) load] |
明明應該最早執行 load 方法的分類,卻統統最後執行,甚至晚於和它沒有啥關係的 ClassMain load。所以分類低人一等,最晚執行 load 方法。
得出結論,load 的執行順序滿足以下幾條
- 執行子類的 load 之前,當父類未載入時,先執行父類的 load 方法。
- 分類的 load 方法統一在最後執行
- 優先滿足以上兩條,再滿足按 Compile Sources 的順序執行 load 方法。
initialize 方法的執行順序
這個時候來測試 initialize 的執行順序,在 ClassFather ClassSon 以及他們的分類都重寫 initialize 方法,並將 ClassFather 移動至 ClassSon 的前面,在 ClassFather load 裡新增呼叫 ClassSon 的類方法的程式碼,如下
1 2 3 4 |
+ (void)load { NSLog(@"%s", __func__); [ClassSon method]; } |
執行結果如下
1 2 3 4 5 6 7 8 9 10 11 12 |
JMTestDemo[15325:1836741] +[ClassFather load] JMTestDemo[15325:1836741] +[ClassFather(category) initialize] JMTestDemo[15325:1836741] +[ClassSon(category) initialize] JMTestDemo[15325:1836741] +[ClassSon method] JMTestDemo[15325:1836741] +[ClassSon load] JMTestDemo[15325:1836741] +[ViewController load] JMTestDemo[15325:1836741] +[AppDelegate load] JMTestDemo[15325:1836741] +[ClassA load] JMTestDemo[15325:1836741] +[ClassMain load] JMTestDemo[15325:1836741] +[ClassSon(category2) load] JMTestDemo[15325:1836741] +[ClassSon(category) load] JMTestDemo[15325:1836741] +[ClassFather(category) load] |
從執行結果來看,先執行了 ClassFather(category) initialize,再執行了 ClassSon(category) initialize,而 ClassSon load 在後面執行。
也就是說 load 方法還未執行也不會影響到這個類的使用。
另一個現象是執行子類 initialize 的時候會先執行其父類的 initialize。且 category 的覆寫效應對 load 方法無效,但對 initialize 方法有效。且按 Complile Sources 的順序,ClassSon(category2) 先覆寫了 ClassSon 的 initialize 方法,接著 ClassSon(category) 覆寫了 ClassSon(category2) 的 initialize。
如果將子類以及類別的 initialize 註釋掉,再修改 ClassFather(category) initialize ,如下
1 2 3 |
+ (void)initialize { NSLog(@"呼叫者:%@ 呼叫方法:%s",NSStringFromClass(self), __func__); } |
結果如下
1 2 3 4 |
JMTestDemo[15458:1863222] +[ClassFather load] JMTestDemo[15458:1863222] 呼叫者:ClassFather 呼叫方法:+[ClassFather(category) initialize] JMTestDemo[15458:1863222] 呼叫者:ClassSon 呼叫方法:+[ClassFather(category) initialize] JMTestDemo[15458:1863222] +[ClassSon method] |
也就是子類會繼承父類的 initialize 。當執行完父類的 initialize 方法,準備執行子類的 initialize 方法時,會根據繼承鏈找到父類的 initialize 執行。為了防止重複執行 initialize 裡的程式碼,可以根據呼叫者來決定是否執行 initialize 裡的其它程式碼。
類和物件
這塊寫了一部分,但查資料的時候查到寫的非常不錯的,我覺得我沒有寫的必要了,留下連結,強烈建議想對 iOS 開發中的類和物件有更深瞭解的人看看。
從 NSObject 的初始化了解 isa
深入解析 ObjC 中方法的結構