24·iOS 面試題·+(void)load; +(void)initialize; 有什麼用處?

weixin_33785972發表於2018-11-22

前言

寫在最前面,Objective-C +load vs +initialize 這篇部落格已經非常完美的回答了這道題目,我這篇文章就是學習完之後的學習筆記。強烈建議大家去閱讀 Objective-C +load vs +initialize

+load 方法

+load 方法呼叫時機

+load 方法是當類被新增 Objective-C Runtime 時呼叫的,類呼叫順序如下:

  1. 父類的 +load 方法
  2. 子類的 +load 方法
  3. 分類的 +load 方法(有多個分類,呼叫順序是看分類檔案的編譯順序)

+load 方法被呼叫的方式

下面是 runtime 中,呼叫 +load 方法最核心的原始碼:

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_method)(cls, SEL_load); ,這裡是直接使用函式記憶體地址方式呼叫,而不是用 objc_masSend 發訊息的機制;

直接利用函式地址呼叫的方法,沒有走訊息轉發機制,就會出現這樣子的現象:父類、子類、分類的 +load 互不影響,假設子類沒有實現 +load 方法,也不會去呼叫多一次父類的 +load 方法。

+load 方法可以做什麼

由於 +load 方法是在 Runtime 載入類時呼叫的,執行時機是比較早的,在這裡我們可以做 Method Swizzling,對一些函式進行 Hook。(無痕埋點、AOP 都可以利用這個機制來實現)

+initialize 方法

+initialize 方法呼叫時機

+initialize 方法是在類或它的子類收到第一條訊息之前被呼叫的,runtime 原始碼如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    ...
        rwlock_unlock_write(&runtimeLock);
    }

    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 
    ...
}

當給一個類傳送訊息的時候,runtime 會呼叫 lookUpImpOrForward 函式,這裡會對沒有初始過的類進行初始化,呼叫 _class_initialize 函式。

+initialize 方法被呼叫的方式

下面是 runtime 中,呼叫 +initialize 方法最核心的原始碼:

void _class_initialize(Class cls)
{
    ...
    Class supercls;
    BOOL reallyInitialize = NO;

    // 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());
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
    ...
}

通過這個函式,我們可以知道兩點:

  1. 遞迴呼叫 +initialize 方法的,父類比子類先初始化
  2. 利用 objc_msgSend 發訊息模式呼叫 +initialize 方法的(這裡與其它普通方法呼叫一致)

故我們可以知道,如果子類沒有實現 +initialize 方法,那麼繼承自父類的實現會被呼叫;如果一個類的分類實現了 +initialize 方法,那麼就會對這個類中的實現造成覆蓋。

+initialize 方法可以做什麼

+initialize 方法呼叫的時機也是非常早的,並且是懶載入模式,可以在 +initialize 方法裡面做一些初始化的工作。

+load 方法 和 +initialize 方法比較

這裡直接用 Objective-C +load vs +initialize 中的總結:

+load +initialize
呼叫時機 被新增到 runtime 時 收到第一條訊息前,可能永遠不呼叫
呼叫順序 父類->子類->分類 父類->子類
呼叫次數 1次 多次
是否需要顯式呼叫父類實現
是否沿用父類的實現
分類中的實現 類和分類都執行 覆蓋類中的方法,只執行分類的實現

總結

通過這個面試題,在網上找到了這篇部落格:Objective-C +load vs +initialize ,又學習到知識點了,開心。

參考文獻

Objective-C +load vs +initialize

01·iOS 面試題·專案中用過 Runtime 嗎?

03·iOS 面試題·main()之前的過程有哪些?

06·iOS 面試題·Category 中有 load 方法嗎?load 方法是什麼時候呼叫的?load 方法能繼承嗎?

相關文章