Objective-C Runtime類的加載與初始化(+ load;+ initialize)

我們知道,iOS App的 main() 函數(shù)位于 main.m 中,這是我們熟知的程序入口,但是在這之前, 還要先進(jìn)行加載 framework 、初始化 runtime 等操作, framework 的加載是由 dylb 調(diào)用的,關(guān)于 dylb ,這里不作探討,感興趣的同學(xué)可以看看這篇文章(dyld: Dynamic Linking On OS X )。本文結(jié)合 runtime 源碼,對(duì)類的加載和初始化中涉及到的兩個(gè)方法 +load+initialize 進(jìn)行探討,了解他們的調(diào)用時(shí)機(jī)和調(diào)用方式,

所用到的runtime源碼為objc4-680.tar.gz 。

load

runtime 的初始化函數(shù)在 objc-os.mm 中的 _objc_init 中, 這個(gè)方法在 old-ABI 中是由 dylb 在初始化動(dòng)態(tài)鏈接庫(kù)的時(shí)候調(diào)用的,現(xiàn)在是由 libSystem 在動(dòng)態(tài)鏈接庫(kù)初始化之前調(diào)用的:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Old ABI: called by dyld as a library initializer
* New ABI: called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    ......
    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_2_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

在這里, runtime 先會(huì)調(diào)用 map_2_images 方法, 加載 images 到內(nèi)存中, category 中擴(kuò)展的屬性和方法就是在這個(gè)時(shí)候附加到類上的,這將在后續(xù)的文章中進(jìn)行探討。然后會(huì)調(diào)用 load_images 這個(gè)方法:

const char *load_images(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[])
{
    bool found;
    // Return without taking locks if there are no +load methods here.
    found = false;
    ......
    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        //先load images
        found = load_images_nolock(state, infoCount, infoList);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    if (found) {
               //然后調(diào)用類的+load方法
        call_load_methods();
    }
    return nil;
}

load_images 在這個(gè)方法里先是調(diào)用 load_images_nolock 方法, 在這個(gè)方法里會(huì)調(diào)用 prepare_load_methods 方法去準(zhǔn)備好要被調(diào)用的 +load 方法,我們先來(lái)看下 prepare_load_methods 方法的實(shí)現(xiàn):

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &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);
    }
}

在這里,先是 schedule_class_load(Class cls) 方法去準(zhǔn)備好所有滿足 +load 方法調(diào)用條件的類,這個(gè)方法會(huì)對(duì)入?yún)⒌母割愡M(jìn)行遞歸調(diào)用,以確保父類優(yōu)先的順序:

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

當(dāng) prepare_load_methods 函數(shù)執(zhí)行完之后,所有滿足 +load 方法調(diào)用條件的類和分類就被分別保存在全局變量 loadable_classesloadable_categories 中了。

準(zhǔn)備好類和分類之后,接下來(lái)就是對(duì)他們的 +load 方法進(jìn)行調(diào)用了,找到 call_load_methods 方法:

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

    loadMethodLock.assertLocked();

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

在這個(gè)方法里,會(huì)以類優(yōu)先于分類的順序調(diào)用 +load 方法

這里有兩個(gè)關(guān)鍵的函數(shù) call_class_loads()call_category_loads

這兩個(gè)函數(shù)會(huì)遍歷上一步中準(zhǔn)備好的 loadable_classesloadable_categories+load 方法,需要注意的是他們都是以函數(shù)內(nèi)存地址的方式 ((*load_method)(cls, SEL_load)) 對(duì) +load 方法進(jìn)行調(diào)用的,而不是使用發(fā)送消息 objc_msgSend 的方式.

這樣的調(diào)用方式就使得 +load 方法擁有了一個(gè)特性,那就是子類、父類和分類中的 +load 方法的實(shí)現(xiàn)是被區(qū)別對(duì)待的。也就是說(shuō)如果子類沒(méi)有實(shí)現(xiàn) +load 方法,那么當(dāng)它被加載時(shí) runtime 是不會(huì)去調(diào)用父類的 +load 方法的。同理,當(dāng)一個(gè)類和它的分類都實(shí)現(xiàn)了 +load 方法時(shí),兩個(gè)方法都會(huì)被調(diào)用,多個(gè)分類實(shí)現(xiàn)了 +load 方法時(shí),每個(gè)分類的 +load 方法都會(huì)被調(diào)用。

initialize

+initialize 方法是在類或類的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用。

也就是說(shuō) +initialize 方法是以懶加載的方式被調(diào)用的,如果一直沒(méi)有給一個(gè)類或他的子類發(fā)送消息,那么這個(gè)類的 +initialize 方法是永遠(yuǎn)不會(huì)調(diào)用的。

當(dāng)我們向某個(gè)類發(fā)送消息時(shí),runtime 會(huì)調(diào)用 IMP lookUpImpOrForward(...) 這個(gè)函數(shù)在類中查找相應(yīng)方法的實(shí)現(xiàn)或進(jìn)行消息轉(zhuǎn)發(fā),打開(kāi) objc-runtime-new.h 找到這個(gè)函數(shù):

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    ...
    if (initialize  &&  !cls->isInitialized()) {
        // 類沒(méi)有初始化時(shí),對(duì)類進(jìn)行初始化
        _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
    }

    ...
}

從中可以看到當(dāng)類沒(méi)有初始化時(shí),會(huì)調(diào)用 _class_initialize(Class cls) 對(duì)類進(jìn)行初始化:

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

    Class supercls;
    BOOL reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    // 遞歸調(diào)用,對(duì)父類進(jìn)行_class_initialize調(diào)用,確保父類的initialize方法比子類先調(diào)用
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    ......
    
    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());
        }
        // 發(fā)送調(diào)用類方法initialize的消息
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        ......
}

在這里,先是對(duì)入?yún)⒌母割愡M(jìn)行遞歸調(diào)用,以確保父類優(yōu)先于子類初始化

還有一個(gè)關(guān)鍵的地方: runtime 使用了發(fā)送消息 objc_msgSend 的方式對(duì) +initialize 方法進(jìn)行調(diào)用,這樣, +initialize 方法的調(diào)用就與普通方法的調(diào)用是一致的,都是走的發(fā)送消息的流程,所以,如果子類沒(méi)有實(shí)現(xiàn) +initialize 方法,將會(huì)沿著繼承鏈去調(diào)用父類的 +initialize 方法,同理,分類中的 +initialize 方法會(huì)覆蓋原本類的方法。

雖然對(duì)每個(gè)類只會(huì)調(diào)用一次 _class_initialize(Class cls) 方法,但是由于 +initialize 方法的調(diào)用走的是消息發(fā)送的流程,當(dāng)某個(gè)類有多個(gè)子類時(shí),這個(gè)類的 +initialize 方法有可能會(huì)被多次調(diào)用,這時(shí),可能需要在 +initialize 方法中判斷是否是由子類調(diào)用的:

+ (void)initialize{
    if (self == [ClassName class]) {
        ......
    }
}

總結(jié):

  1. +load 方法在類加載時(shí)被調(diào)用,+initialize 方法在類收到第一條消息時(shí)調(diào)用,可能永遠(yuǎn)不會(huì)調(diào)用

  2. 兩個(gè)方法的調(diào)用順序都是先父類后子類,

  3. +load 方法在 runtime 中是直接以函數(shù)地址的方式進(jìn)行調(diào)用,如果有多個(gè)分類,所有分類的 +load 方法也會(huì)被調(diào)用,并且在類的 +load 方法之后被調(diào)用,多個(gè)分類的 +load 方法調(diào)用順序取決于編譯的順序

  4. +initialize 方法在 runtime 中是以發(fā)送消息的方式調(diào)用的,所以子類會(huì)覆蓋父類的實(shí)現(xiàn),分類會(huì)覆蓋類的實(shí)現(xiàn),多個(gè)分類只會(huì)調(diào)用一個(gè)分類的 +initialize 方法。

參考鏈接

本文出自 kingizz's blog - Objective-C Runtime(三)類的加載與初始化 轉(zhuǎn)載請(qǐng)注明出處。

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