細說OC中的load和initialize方法

OC中有兩個特殊的類方法,分別是loadinitialize。本文總結一下這兩個方法的區別於聯繫、使用場景和注意事項。Demo能夠在個人Github上找到——load和initialize,若是以爲有幫助還望點個關注以示支持,總結在文章末尾。html

先來看看NSObject Class Reference裏對這兩個方法說明:ios

+(void)initialize

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.

  initialize方法不必定會執行。只有當一個類第一次被髮送消息的時候會執行,注意是第一次。什麼叫發送消息呢,就是執行類的一些方法的時候。也就是說這個方法是懶加載,沒有用到這個類就不會調用,能夠節省系統資源。還有一點截然相反,卻更符合咱們預期的就是,initialize方法會覆蓋。也就是說若是子類實現了initialize方法,就不會執行父類的了,直接執行子類自己的。若是分類實現了initialize方法,也不會再執行主類的。因此initialize方法的執行覆蓋順序是 分類 -> 子類 ->類。且只會有一個initialize方法被執行。git

調用規則

load方法相似的是,在initialize方法內部也會調用父類的方法,並且不須要咱們顯示的寫出來。與load方法不一樣之處在於,即便子類沒有實現initialize方法,也會調用父類的方法,這會致使一個很嚴重的問題:github

// In Parent.m
+ (void)initialize {
    NSLog(@"Initialize Parent, caller Class %@", [self class]);
}

// In Child.m
// 註釋掉initialize方法

// In main.m
Child *child = [Child new];

運行後發現父類的initialize方法居然調用了兩次:安全

2017-04-05 22:25:56.056 load[742:32295] Initialize Parent, caller Class Parent
2017-04-05 22:25:57.179 load[742:32295] Initialize Parent, caller Class Child

這是由於在建立子類對象時,首先要建立父類對象,因此會調用一次父類的initialize方法,而後建立子類時,儘管本身沒有實現initialize方法,但仍是會調用到父類的方法。app

雖然initialize方法對一個類而言只會調用一次,但這裏因爲出現了兩個類,因此調用兩次符合規則,但不符合咱們的需求。正確使用initialize方法的姿式以下:函數

// In Parent.m
+ (void)initialize {
    if (self == [Parent class]) {
        NSLog(@"Initialize Parent, caller Class %@", [self class]);
    }
}

加上判斷後,就不會由於子類而調用到本身的initialize方法了。this

使用場景

initialize方法主要用來對一些不方便在編譯期初始化的對象進行賦值。好比NSMutableArray這種類型的實例化依賴於runtime的消息發送,因此顯然沒法在編譯器初始化:spa

// In Parent.m
static int someNumber = 0;     // int類型能夠在編譯期賦值
static NSMutableArray *someObjects;

+ (void)initialize {
    if (self == [Parent class]) {
        // 不方便編譯期複製的對象在這裏賦值
        someObjects = [[NSMutableArray alloc] init];
    }
}

+(void)load

The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond. The order of initialization is as follows: All initializers in any framework you link to. All +load methods in your image. All C++ static initializers and C/C++ __attribute__(constructor) functions in your image. All initializers in frameworks that link to you. In addition: A class’s +load method is called after all of its superclasses’ +load methods. A category +load method is called after the class’s own +load method. In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.

 顧名思義,load方法在這個文件被程序裝載時調用。只要是在Compile Sources中出現的文件老是會被裝載,這與這個類是否被用到無關,所以load方法老是在main函數以前調用。 線程

調用規則

 若是一個類實現了load方法,在調用這個方法前會首先調用父類的load方法。並且這個過程是自動完成的,並不須要咱們手動實現:

// In Parent.m
+ (void)load {
    NSLog(@"Load Class Parent");
}

// In Child.m,繼承自Parent
+ (void)load {
    NSLog(@"Load Class Child");
}

// In Child+load.m,Child類的分類
+ (void)load {
    NSLog(@"Load Class Child+load");
}
/*
2017-04-05 19:14:14.328 load[11739:419339] Load Class Parent
2017-04-05 19:14:14.329 load[11739:419339] Load Class Child
2017-04-05 19:14:14.330 load[11739:419339] Load Class Child+load
*/

若是一個類沒有實現load方法,那麼就不會調用它父類的load方法,這一點與正常的類繼承和方法調用不同,須要額外注意一下。

執行順序

load方法調用時,系統處於脆弱狀態,若是調用別的類的方法,且該方法依賴於那個類的load方法進行初始化設置,那麼必須確保那個類的load方法已經調用了,好比demo中的這段代碼,打印出的字符串就爲null

// In Child.m
+ (void)load {
    NSLog(@"Load Class Child");

    Other *other = [Other new];
    [other originalFunc];

    // 若是不先調用other的load,下面這行代碼就無效,打印出null
    [Other printName];
}

 

 

首先,若是全部類都繼承NSObject沒有繼承關係,load方法的調用順序是

從上到下依次執行loda方法而後再執行main方法。若是類之間有繼承關係,先調用父類的load方法。load方法是必定會在runtime中被調用的,只要類被添加到runtime中了,就會調用load方法,因此咱們能夠本身實現laod方法來在這個時候執行一些行爲。並且有意思的一點是,load方法不會覆蓋。也就是說,若是子類實現了load方法,那麼會先調用父類的load方法,而後又去執行子類的load方法。一樣的,若是分類實現了load方法,也會先執行主類的load方法,而後又會去執行分類的load方法。因此父類的load會執行不少次,這一點須要注意。並且執行順序是 類 -> 子類 ->分類。而不一樣類之間的順序不必定。

注意事項:
雖然在這種簡單狀況下咱們能夠辨別出各個類的方法調用的順序,但永遠不要依賴這個順序完成你的代碼邏輯。一方面,這在後期的開發中極容易致使錯誤,另外一方面,你實際上並不須要這麼作。load

使用場景:

因爲調用方法時的環境很不安全,咱們應該儘可能減小方法的邏輯。另外一個緣由是方法是線程安全的,它內部使用了鎖,因此咱們應該避免線程阻塞在方法中。

一個常見的使用場景是在方法中實現Method Swizzle:
loadloadloadloadload
// In Other.m
+ (void)load {
    Method originalFunc = class_getInstanceMethod([self class], @selector(originalFunc));
    Method swizzledFunc = class_getInstanceMethod([self class], @selector(swizzledFunc));

    method_exchangeImplementations(originalFunc, swizzledFunc);
}
在類的方法中,因爲還沒調用的方法,因此輸出結果是"Original Output",而在main函數中,輸出結果天然就變成了"Swizzled Output"。
提示:通常來講,除了Method Swizzle,別的邏輯都不該該放在方法中實現。
ChildloadOtherloadload

小結:

Apple的文檔很清楚地說明了initialize和load的區別在於:load是隻要類所在文件被引用就會被調用,而initialize是在類或者其子類的第一個方法被調用前調用。因此若是類沒有被引用進項目,就不會有load調用;但即便類文件被引用進來,可是沒有使用,那麼initialize也不會被調用。

它們的相同點在於:方法只會被調用一次。(其實這是相對runtime來講的,後邊會作進一步解釋)。

文檔也明確闡述了方法調用的順序:父類(Superclass)的方法優先於子類(Subclass)的方法,類中的方法優先於類別(Category)中的方法。

不過還有不少地方是文章中沒有解釋詳細的。因此再來看一些示例代碼來明確其中應該注意的細節。

 

總結 

  1. loadinitialize方法都會在實例化對象以前調用,以main函數爲分水嶺,前者在main函數以前調用,後者在以後調用。這兩個方法會被自動調用,不能手動調用它們。
  2. loadinitialize方法都不用顯示的調用父類的方法而是自動調用,即便子類沒有initialize方法也會調用父類的方法,而load方法則不會調用父類。
  3. load方法一般用來進行Method Swizzle,initialize方法通常用於初始化全局變量或靜態變量。
  4. loadinitialize方法內部使用了鎖,所以它們是線程安全的。實現時要儘量保持簡單,避免阻塞線程,不要再使用鎖。

  舒適提示:親手本身用代碼嘗試下代碼,運行幾回會更加明白。Demo能夠在個人Github上找到—load和initialize(若有問題你們一塊兒商討)

相關文章
相關標籤/搜索