Objective-C +load vs +initialize

+load

+load 方法是當(dāng)類或分類被添加到 Objective-C runtime 時(shí)被調(diào)用的,實(shí)現(xiàn)這個(gè)方法可以讓我們?cè)陬惣虞d的時(shí)候執(zhí)行一些類相關(guān)的行為。子類的 +load 方法會(huì)在它的所有父類的 +load 方法之后執(zhí)行,而分類的 +load 方法會(huì)在它的主類的 +load 方法之后執(zhí)行。但是不同的類之間的 +load 方法的調(diào)用順序是不確定的,根據(jù)編譯順序來(lái)決定。

+initialize

+initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用。也就是說(shuō) +initialize 方法是以懶加載的方式被調(diào)用的,如果程序一直沒(méi)有給某個(gè)類或它的子類發(fā)送消息,那么這個(gè)類的 +initialize 方法是永遠(yuǎn)不會(huì)被調(diào)用的。那這樣設(shè)計(jì)有什么好處呢?好處是顯而易見(jiàn)的,那就是節(jié)省系統(tǒng)資源,避免浪費(fèi)。

總結(jié)

通過(guò)閱讀 runtime 的源碼,我們知道了 +load 和 +initialize 方法實(shí)現(xiàn)的細(xì)節(jié),明白了它們的調(diào)用機(jī)制和各自的特點(diǎn)。下面我們繪制一張表格,以更加直觀的方式來(lái)鞏固我們對(duì)它們的理解:

+load
調(diào)用時(shí)機(jī) 被添加到 runtime 時(shí)
調(diào)用順序 父類->子類->分類
調(diào)用次數(shù) 1次
是否需要顯式調(diào)用父類實(shí)現(xiàn) 否
是否沿用父類的實(shí)現(xiàn) 否
分類中的實(shí)現(xiàn) 類和分類都執(zhí)行

+initialize
調(diào)用時(shí)機(jī) 收到第一條消息前,可能永遠(yuǎn)不調(diào)用
調(diào)用順序 父類->子類
調(diào)用次數(shù) 多次
是否需要顯式調(diào)用父類實(shí)現(xiàn) 否
是否沿用父類的實(shí)現(xiàn) 是
分類中的實(shí)現(xiàn) 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn)

原理

“源碼面前沒(méi)有秘密”。最后,我們來(lái)看看蘋(píng)果開(kāi)放出來(lái)的部分源碼。從中我們也許能明白為什么load和initialize及調(diào)用會(huì)有如上的一些特點(diǎn)。

其中l(wèi)oad是在objc庫(kù)中一個(gè)load_images函數(shù)中調(diào)用的,先把二進(jìn)制映像文件中的頭信息取出,再解析和讀出各個(gè)模塊中的類定義信息,把實(shí)現(xiàn)了load方法的類和Category記錄下來(lái),最后統(tǒng)一執(zhí)行調(diào)用。

其中的prepare_load_methods函數(shù)實(shí)現(xiàn)如下:

 void prepare_load_methods(header_info *hi)
{
     size_t count, i;

     rwlock_assert_writing(&runtimeLock);

     classref_t *classlist = 
     _getObjc2NonlazyClassList(hi, &count);
     for (i = 0; i < count; i++) {
          schedule_class_load(remapClass(classlist[i]));
     }

     category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
     for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
     }
}

這大概就是主類中的load方法先于category的原因。再看下面這段:

 static void schedule_class_load(Class cls)
 {
       if (!cls) return;
       assert(cls->isRealized());  // _read_images should realize

       if (cls->data()->flags & RW_LOADED) return;

       // Ensure superclass-first ordering
       schedule_class_load(cls->superclass);

       add_class_to_loadable_list(cls);
       cls->setInfo(RW_LOADED); 
 }

這正是父類load方法優(yōu)先于子類調(diào)用的原因。

再來(lái)看下initialize調(diào)用相關(guān)的源碼。objc的庫(kù)里有一個(gè)_class_initialize方法實(shí)現(xiàn),如下:

 void _class_initialize(Class cls)
 {
      assert(!cls->isMetaClass());

      Class supercls;
      BOOL reallyInitialize = NO;

      supercls = cls->superclass;
      if (supercls  &&  !supercls->isInitialized()) {
           _class_initialize(supercls);
      }

      monitor_enter(&classInitLock);
      if (!cls->isInitialized() && !cls->isInitializing()) {
          cls->setInitializing();
          reallyInitialize = YES;
      }
      monitor_exit(&classInitLock);

      if (reallyInitialize) {
          _setThisThreadIsInitializingClass(cls);

          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]",
                     cls->nameForLogging());
          }

          monitor_enter(&classInitLock);
          if (!supercls  ||  supercls->isInitialized()) {
              _finishInitializing(cls, supercls);
          } else {
              _finishInitializingAfter(cls, supercls);
          }
          monitor_exit(&classInitLock);
          return;
     }

     else if (cls->isInitializing()) {
         if (_thisThreadIsInitializingClass(cls)) {
              return;
         } else {
              monitor_enter(&classInitLock);
              while (!cls->isInitialized()) {
                   monitor_wait(&classInitLock);
              }
              monitor_exit(&classInitLock);
              return;
         }
    }

    else if (cls->isInitialized()) {
         return;
    }

    else {
         _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
   }

在這段代碼里,我們能看到initialize的調(diào)用順序和線程安全性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容