iOS中的+load 與 +initialize

在iOS中,所有的類都繼承自NSObject,在這個(gè)類中,有兩個(gè)類方法,分別是 +load 與 +initialize,他們承擔(dān)著不同的任務(wù),今天詳細(xì)的解讀一下它們各自的意義以及調(diào)用原理。

+load

load方法會(huì)先調(diào)用所有父類+load,然后調(diào)用子類+load,最后調(diào)用分類 +load,分類的調(diào)用順序由 Compile Source決定

從 runtime的源碼來(lái)解開(kāi)+load方法調(diào)用的原理。

首先**void prepare_load_methods(header_info *hi) **函數(shù):

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

函數(shù)主要作用是準(zhǔn)備好被調(diào)用的類和分類,具體實(shí)現(xiàn)方法在 schedule_class_load(remapClass(classlist[i]));函數(shù)中。

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

schedule_class_load(cls->superclass);中可以看出,每次都傳入父類作為參數(shù)遞歸的調(diào)用,決定了方法的調(diào)用順序,父類有限,待方法調(diào)用完成,會(huì)在全局變量 loadable_classesloadable_categories 中。

當(dāng)類準(zhǔn)備好,接下來(lái)調(diào)用+load方法,void call_load_methods(void) 函數(shù)

void call_load_methods(void)
{
    static BOOL loading = NO;
    BOOL more_categories;

    recursive_mutex_assert_locked(&loadMethodLock);

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

其中,主類的調(diào)用順序優(yōu)于分類的調(diào)用,具體調(diào)用方法在 call_class_loads();call_category_loads();中。

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

在這個(gè)方法中可以看到,使用方法首地址進(jìn)行直接調(diào)用,并非使用objc_msgSend,通過(guò)這個(gè)方式,我們可以在分類中做一些事情,例如 Method Swizzling

+initialize

+initialize 方法會(huì)在類收到第一個(gè)消息時(shí)調(diào)用,是一個(gè)懶加載的方式,如果一直沒(méi)收到消息,則永不調(diào)用。這種設(shè)計(jì)節(jié)省了資源。

objc-runtime-new.mm中我們可以看到。

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

當(dāng)向類發(fā)送消息時(shí),如果類沒(méi)有初始化,則會(huì)調(diào)用初始化方法 void _class_initialize(Class cls)

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]",
    ...
}

這里我們可以看出,其實(shí)+initialize本質(zhì)為objc_msgSend,走的是方法調(diào)用的流程,如子類沒(méi)有實(shí)現(xiàn)+initialize,則會(huì)調(diào)用父類+initialize方法,如分類實(shí)現(xiàn)了+initialize方法,則會(huì)覆蓋主類方法(這里的覆蓋不是真正的覆蓋,主類的方法還存在方法列表中,只是runtime命中對(duì)應(yīng)方法后不會(huì)繼續(xù)向下搜索,則直接調(diào)用該方法)。

我們可以避免父類+initialize方法多次執(zhí)行

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

總結(jié):

+load +initialize
調(diào)用時(shí)機(jī) 添加到runtime時(shí) 第一次發(fā)送消息
調(diào)用順序 父類->子類->分類 父類->子類(可被分類覆蓋)
調(diào)用次數(shù) 1 N
是否需要顯式調(diào)用父類實(shí)現(xiàn) NO NO
是否沿用父類的實(shí)現(xiàn) NO YES
分類中的實(shí)現(xiàn) 都執(zhí)行 如覆蓋,不執(zhí)行主類

參考鏈接:
http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/
https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html
http://blog.iderzheng.com/objective-c-load-vs-initialize/
http://nshipster.com/method-swizzling/

最后編輯于
?著作權(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)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評(píng)論 0 9
  • Objective C類方法load和initialize的區(qū)別過(guò)去兩個(gè)星期里,為了完成一個(gè)工作,接觸到了NSOb...
    亦晴工作室閱讀 1,434評(píng)論 0 10
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,872評(píng)論 33 466
  • 之前在寫《Category你真的懂嗎?》那篇簡(jiǎn)書(shū)收集資料的時(shí)候,看了很多l(xiāng)oad和initialize的資料,加深...
    一劍孤城閱讀 2,803評(píng)論 3 24
  • 今天,感覺(jué)主動(dòng)確實(shí)有蝴蝶效應(yīng)…… 可以掌握主動(dòng)權(quán). 學(xué)習(xí)和放松應(yīng)該適合…… 學(xué)習(xí)有厭惡感時(shí)可以聽(tīng)聽(tīng)音樂(lè). 主動(dòng)也可...
    簡(jiǎn)潔一方閱讀 206評(píng)論 0 0

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