iOS:淺談 +load()、+initialize()、-init()

對於 iOS 開發者而言,+load()+initialize()-init() 方法應該並不陌生,可是,對於這些方法的調用邏輯和順序,可能會偶有疑惑,本文經過 demo 的方式,來探究一下這幾個方法

+ load()

+load() 方法是當類或分類被添加到 Objective-C runtime 時被調用的,實現這個方法可讓咱們在類加載的時候執行一些類相關的行爲,子類的 +load 方法會在它的全部父類的 +load() 方法以後執行,而分類的 +load() 方法會在它的主類的 +load() 方法以後執行。可是不一樣的類之間的 +load()方法的調用順序是不肯定的 安全

image.png
咱們先建立一個 TestClass 類,而後在建立一個繼承自該類的子類 TestClassSubClass,和兩個 TestClass 的分類 TestClass+ZTestClass+Y,分別在這幾個類中實現 +load() 方法,並打印輸出,而後在 main() 中也寫一個輸出語句,運行程序,咱們來看一下輸出

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        NSLog(@" %s", __func__);
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
複製代碼

image.png

如咱們所看到的,+load() 的調用是在 main() 函數以前,而且在整個 APP 運行過程當中只會被調用一次,對 +load() 方法進行調用,是直接使用函數內存地址的方式 (load_method)(cls, SEL_load); 而不是使用發送消息 objc_msgSend 的方式,父類 TestClass+load()方法最早被調用,然後是子類和分類,對於多個分類的 +load() 的調用順序的前後,取決於編譯順序,測試一下,咱們在 Bulid Phases->Compile Sources 調整分類的編譯順序 bash

image.png

image.png
會看到 TestClass+Z+load() 方法先於 TestClass+Y 被調用

結論:
  • +load() 方法的調用是在 main() 函數以前,而且不須要主動調用,程序啓動會把全部的文件加載,文件若是重寫了 +load() 方法,主類、子類、分類都會加載調用 +load() 方法;
  • 主類與分類的加載順序是: 主類優先於分類加載,無關編譯順序;
  • 分類間的加載順序取決於編譯的順序: 先編譯先加載,後編譯則後加載;
  • 優先順序: (父類 > 子類 > 分類)
  • 由於 +load() 是在 main() 函數以前調用,因此在這個方法裏面不要做耗時操做或者阻塞的操做,會影響啓動速度;
  • 不要作對象的初始化操做,由於在 main() 函數以前自動調用,+load() 方法調用的時候使用者根本就不能肯定本身要使用的對象是否已經加載進來了,因此千萬不能在這裏初始化對象;
  • 能夠根據業務需求,在 +load() 方法中進行 Method Swizzle 操做,交換方法

+ initialize()

+initialize() 方法是在類或它的子類收到第一條消息以前被調用的,這裏所指的消息包括實例方法和類方法的調用,也就是說 +initialize() 方法是以懶加載的方式被調用的,若是程序一直沒有給某個類或它的子類發送消息,那麼這個類的 +initialize() 方法是永遠不會被調用的,關於調用,咱們一樣用代碼進行測試函數

  • TestClass 實現 + initialize() 方法,子類和分類中不實現,分別以下調用
TestClass *class1 = [[TestClass alloc] init];
      TestClass *class2 = [[TestClass alloc] init];
    
//    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
//    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
//    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
//    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
//    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png

TestClass *class1 = [[TestClass alloc] init];
    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png

//    TestClass *class1 = [[TestClass alloc] init];
//    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png

會發現,無論咱們建立 TestClass 幾個對象,[TestClass initialize]只被調用一次,而若是建立了 TestClassSubClass 對象,不管是否建立 TestClass 對象,[TestClass initialize] 都會調用兩次,可見當子類未實現 +initialize() 方法,會調用父類 +initialize() 方法。測試

  • TestClass 和子類中分別實現 + initialize 方法
TestClass *class1 = [[TestClass alloc] init];
    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png

//    TestClass *class1 = [[TestClass alloc] init];
//    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png
可見,不管子類中是否實現了 + initialize() 方法,初始化都會至少去調用一次父類的 + initialize() 方法,而若是自身實現了 + initialize() 方法,那麼繼續調用本身的 + initialize() 方法,若是未實現,則去調用父類的 + initialize()方法

  • TestClass 和子類、分類中分別實現 + initialize() 方法
TestClass *class1 = [[TestClass alloc] init];
    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼
//    TestClass *class1 = [[TestClass alloc] init];
//    TestClass *class2 = [[TestClass alloc] init];
    
    TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png
根據輸出咱們看到,以前的調用父類的 + initialize() 方法,變成了調用 TestClass+Y+ initialize() 方法,這是由於最後被編譯的 TestClass+Y 中的 + initialize() 方法覆蓋了主類的 + initialize() 方法

結論:
  • 父類的 + initialize() 方法會比子類先執行;
  • 當子類未實現 + initialize() 方法時,會調用父類 + initialize() 方法,子類實現 + initialize() 方法時,會覆蓋父類 + initialize() 方法;
  • 當有多個 Category 都實現了 + initialize() 方法,會覆蓋類中的方法,只執行一個(會執行Compile Sources 列表中最後一個 Category+ initialize() 方法)。

- init()

  • TestClass 和子類分別實現 -init()方法
TestClassSubClass *subClass1 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass2 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass3 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass4 = [[TestClassSubClass alloc] init];
    TestClassSubClass *subClass5 = [[TestClassSubClass alloc] init];
複製代碼

image.png
子類初始化時,會先調用父類的 -init() 方法,而後調用自身的 -init() 方法,而且每次初始化時都會調用

  • 只在 TestClass 實現 -init() 方法ui

    調用結果以下 spa

    image.png
    咱們發現,子類未實現 -init() 方法時,每初始化一個對象,只會調用一次父類的 -init() 方法

總結:
  • +load()+initialize() 都會在實例化對象以前調用,前者是在 main() 函數以前,後者是在 main() 函數以後;
  • +load()+initialize() 方法都不會顯式的調用父類的方法而是自動調用,即便子類沒有 +initialize() 方法也會調用父類的方法,+load() 方法不會調用父類;
  • +load()+initialize() 方法內部使用了鎖,所以他們是線程安全的,實現時要儘量簡單,避免線程阻塞,不要再次使用鎖;
  • +load() 方法經常使用來 method swizzle+initialize() 經常用於初始化全局變量和靜態變量。
相關文章
相關標籤/搜索