OC中load和initialize的區別

OC文件在編譯後,類相關的數據結構會保留在目標文件中,在運行時獲得解析和使用。在應用程序運行起來的時候,類的信息會有加載和初始化過程,這個過程就涉及到了類的兩個類方法:loadinitialize。下面咱們就來介紹一下這2個方法的區別。(首先要說明一下,這2個方法是系統調用的,開發者通常不會主動去調用者兩個方法,這麼作也沒有什麼意義,因此後面的講解都是針對系統調用,不考慮主動調用的狀況)。安全

1. load方法

1.1 調用時機

當咱們啓動程序時,參與了編譯的類、分類都會被加載進內存,load方法就是在這個類被加載的時候調用的(前提是這個類有實現load方法),這個過程與這個類是否被使用是無關的,也就是說若是有一個類(MyClass)即便在整個程序中都沒有用到,甚至沒有任何一個文件去引入MyClass的頭文件,MyClass的的load的方法同樣會被調用。等全部的類、分類都加載進內存後纔會調用程序的main函數,因此全部類的load方法都是在main函數以前被調用的。並且每一個類、分類的load方法只會被調用一次。bash

1.2 調用順序

一個程序中若是全部的類、分類都實現了load方法,那麼全部的load方法都會被調用。它們的執行順序遵循如下規則:數據結構

  • 先執行全部類的load方法,再執行全部分類的load方法。
  • 執行類的load方法時,是按照參與編譯的順序,先編譯的類先執行,可是若是某個類是繼承自另外一個類,那麼會先執行父類的load方法個再執行本身的load方法。
  • 執行分類的load方法時,是按照分類參與編譯的順序,先編譯的分類先執行。

關於編譯順序,咱們能夠在項目的Build Phases --> Compile Sources查看,最上面的就最早編譯,咱們能夠拖動文件來調整編譯順序。app

下面舉個例子來看下load方法的執行順序。首先說明一下幾個類的關係:Person類有aaabbb兩個分類,men類繼承自Person類,men也有2個分類cccdddBook類和前面這些類沒有任何關係。函數

2.jpg
解釋:

  • 編譯順序從上到下,上面先編譯,下面後編譯。因爲先執行類的load再執行分類的load,最早參與編譯的類是men,而men繼承自Person,因此最早執行Personload(雖然Person是後參與編譯的,可是它是父類,因此會先執行),而後再執行menload。接着參與編譯的是Book類,因此緊接着就是執行Bookload。再接着參與編譯的類就是Person,因爲它的load方法已經執行過了,此時就不會執行了。
  • 全部的類的load方法都執行完後開始執行分類的load,分類參與編譯的順序是men+ccc-->Person+aaa-->men+ddd-->Person+bbb,因此分類的load方法個也是按照這個順序執行。

1.3 執行方式

咱們知道,當分類中存在和本類中同名的方法時,調用這個方法最終執行的是分類中的方法。那上面就很奇怪了,PersonPerson的分類中都有load方法,按理說調用load方法時最終只會調用其中一個分類的load方法,可結果Person本類和它的2個分類都調用了load方法。ui

這是由於load方法和普通方法調用的方式不同。普通方法調用是經過消息發送機制實現的,會先去類或元類的方法列表中查找,若是找到了方法就執行,若是沒有找到就去父類的方法列表裏面找,只要找到就會終止查找,因此只會執行一次。spa

load方法調用時,每一個類都是根據load方法的地址直接調用,而不會走objc_msgSend函數的方法查找流程,也就是說一個類有實現load方法就執行,沒有就不執行(沒有的話也不會去父類裏面查找)。code

想要了解更加詳細的底層實現流程,能夠去看objc4源碼,這裏提供一下相關函數調用流程以便進行源碼閱讀: 首先從objc-os.mm文件的_objc_init函數開始-->load_images-->prepare_load_methods-->schedule_class_load-->add_class_to_loadable_list-->add_category_to_loadable_list-->call_load_methods-->call_class_loads-->call_category_loads-->(*load_method)(cls, SEL_load)cdn

1.4 實現load方法時要注意什麼

咱們一般在load方法中進行方法交換(Method Swizzle),除此以外,除非真的有必要,咱們儘可能不要在load方法中寫代碼,尤爲不要在load方法中使用其它的類,由於這個時候其它的類可能尚未被加載進內存,隨意使用可能會出問題。對象

若是確實要在load方法寫一些代碼,那也要儘可能精簡代碼,不要作一些耗時或者等待鎖的操做,由於整個程序在執行load方法時都會阻塞,從而致使程序啓動時間過長甚至沒法啓動。

2. initialize方法

2.1 調用時機

initialize方法是在類或它的子類收到第一條消息時被調用的,這裏的消息就是指實例方法或類方法的調用,因此全部類的initialize調用是在執行main函數以後調用的。並且一個類只會調用一次initialize方法。若是一個類在程序運行過程當中一直沒有被使用過,那這個類的initialize方法也就不會被調用,這一點和load方法是不同的。

2.2 調用方式

initialize方法的調用和普通方法調用同樣,也是走的objc_msgSend流程。因此若是一個類和它的分類都實現了initialize方法,那最終調用的會是分類中的方法。

若是子類和父類都實現了initialize方法,那麼會先調用父類的方法,而後調用子類的方法個(這裏注意子類中不須要寫[super initialize]來調用父類的方法,經過查看源碼得知它是在底層實現過程當中主動調用的父類的initialize方法)。

下面看一個例子:

父類Person實現了initializePersonSub1PersonSub2這兩個子類也實現了initializePersonSub3PersonSub4這兩個子類沒有實現了initialize,按照下面的順序實例化對象:

PersonSub1 *ps1 = [[PersonSub1 alloc] init];
Person *person = [[Person alloc] init];
PersonSub2 *ps2 = [[PersonSub2 alloc] init];
PersonSub3 *ps3 = [[PersonSub3 alloc] init];
PersonSub4 *ps4 = [[PersonSub4 alloc] init];

// ***************打印結果***************
2020-01-06 15:52:38.429218+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429250+0800 CommandLine[68706:7207027] +[PersonSub1 initialize]
2020-01-06 15:52:38.429287+0800 CommandLine[68706:7207027] +[PersonSub2 initialize]
2020-01-06 15:52:38.429347+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429380+0800 CommandLine[68706:7207027] +[Person initialize]
複製代碼

看到這個運行結果,有人就有疑問了:不是說一個類只會調用一次initialize方法嗎,爲何這裏Personinitialize方法被調用了3次?

這裏就須要講解一下底層源碼的執行流程了,每一個類都有一個標記記錄這個類是否調用過initialize,我這裏就用一個BOOL類型的isInitialized來表示,而後用selfClass來表示本身的類,用superClass來表示父類,下面我用僞代碼來描述一下底層源碼執行流程:

// 若是本身沒有調用過initialize就執行裏面的代碼
if(!selfClass.isInitialized){ 
    if(!superClass.isInitialized){
        // 若是父類沒有執行過initialize就給父類發消息(一旦成功執行initialize就將父類的isInitialized置爲YES)
        objc_msgSend(superClass,@selector(initialize));
    }
    // 再給本身的類發消息(一旦成功執行initialize就將本身類的isInitialized置爲YES)
    objc_msgSend(selfClass,@selector(initialize));
}
複製代碼

接下來我來按照這個流程來解釋一下上面運行的結果:

  • 首先PersonSub1被使用,而此時PersonSub1isInitialized爲NO,並且父類PersonisInitialized也爲NO,因此先給父類發消息執行initialize,執行完後PersonisInitialized變爲YES。而後PersonSub1執行本身的initialize,執行完後PersonSub1isInitialized變爲YES。因此這一步先打印+[Person initialize],而後打印+[PersonSub1 initialize]
  • 而後是Person實例化,此時PersonisInitialized爲YES,因此不會再調用initialize。因此這一步什麼都沒打印。
  • 接着是PersonSub2實例化,此時PersonSub2isInitialized爲NO,父類PersonisInitialized爲YES,因此只有PersonSub2會執行initialize,執行完後PersonSub2isInitialized變爲YES。因此這一步打印的是+[PersonSub2 initialize]
  • 再接着是PersonSub3實例化,此時PersonSub3isInitialized爲NO,父類PersonisInitialized爲YES,因此只有PersonSub3會執行initialize,可是因爲PersonSub3沒有實現initialize,它就會去父類找這個方法的實現,找到後就執行父類Personinitialize(注意這裏是PersonSub3執行的Person中的initialize,而不是Person執行的),執行完後PersonSub3isInitialized變爲YES。因此這一步打印的是+[Person initialize]。(注意這裏打印的是方法信息,表示執行的是Person中的initialize,而不是說是Person調用的initialize)。
  • 最後是PersonSub4實例化,這一步過程和上面一步是同樣的,執行完後PersonSub4isInitialized變爲YES。這一步打印的是+[Person initialize]

因此最後的結果就是PersonPersonSub1PersonSub2PersonSub3PersonSub4這5個類都執行了一次initialize,雖然從運行結果來看Personinitialize執行了3次,其實後面2次是PersonSub3PersonSub4調用的。

2.3 使用注意事項

雖然使用initialize要比使用load安全(由於在調用initialize時全部類已經被加載進內存了),但咱們仍是要儘可能少用initialize這個方法個,尤爲要謹慎在分類中實現initialize方法,由於若是在分類中實現了,本類實現的initialize方法將不會被調用。實際開發中initialize方法通常用於初始化全局變量或靜態變量。

相關文章
相關標籤/搜索