Objective-C的+load方法調(diào)用原理分析

Objective-C之Category的底層實(shí)現(xiàn)原理
Objective-C的+initialize方法調(diào)用原理分析

別人是這么說的

  • 調(diào)用時(shí)機(jī):+load方法會(huì)在Runtime加載類對(duì)象(class)和分類(category)的時(shí)候調(diào)用
  • 調(diào)用頻率:每個(gè)類對(duì)象、分類的+load方法苟跪,在工程的整個(gè)生命周期中只調(diào)用一次
  • 調(diào)用順序:
    1. 先調(diào)用類對(duì)象(class)的+load方法:
      • 類對(duì)象的load調(diào)用順序是按照 類文件的編譯順序 進(jìn)行先后調(diào)用赫冬;
      • 調(diào)用子類+load之前會(huì)先調(diào)用父類的+load方法
    2. 再調(diào)用分類(category)的+load方法:按照編譯先后順序調(diào)用(先編譯的伐割,先被調(diào)用)
一鸿摇、load方法的調(diào)用時(shí)機(jī)和調(diào)用頻率

+load方法是在程序一啟動(dòng)運(yùn)行萨惑,加載鏡像中的類對(duì)象(class)和分類(category)的時(shí)候就會(huì)調(diào)用剩岳,只會(huì)調(diào)用一次贞滨,不論在項(xiàng)目中有沒有用到該類對(duì)象或者該分類,他們統(tǒng)統(tǒng)都會(huì)先被加載進(jìn)內(nèi)存,因?yàn)轭惖募虞d只有一次晓铆,所以所有的load方法肯定都會(huì)被調(diào)用而且只有一次勺良。下面先上一個(gè)小demo調(diào)試看看:

image.png

CLPerson

CLPerson+Test

CLPerson_Test2

main.m

上圖里面,我創(chuàng)建了一個(gè)person類骄噪,以及它的兩個(gè)分類--CLPerson+Test/CLPerson+Test2尚困,然后給它們都加上兩個(gè)類方法(+load/+test),main.h里面先不加任何代碼跑跑看链蕊。

運(yùn)行日志

從日志看出事甜,雖然整個(gè)工程都沒有import過CLPerson以及它的兩個(gè)分類,但是他們的load方法還是被調(diào)用了滔韵,并且都發(fā)生在main函數(shù)開始之前逻谦,而且+test并沒有被調(diào)用。所以該現(xiàn)象間接證明了陪蜻,load方法的調(diào)用應(yīng)該和類對(duì)象以及分類的加載有關(guān)跨跨。

main.h里面調(diào)一下+test方法

調(diào)用一下+test方法

+test方法只調(diào)用了一次,并且調(diào)用的是分類的+test方法囱皿,這就是所謂的“覆蓋”現(xiàn)象
接下來通過源碼分析一下(Runtime源碼下載地址)

首先勇婴,進(jìn)入Runtime的初始化文件objc-os.mm,找到_objc_init函數(shù)嘱腥,該函數(shù)可以看作是Runtime的初始化函數(shù)耕渴。

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

忽略一些與本文主題關(guān)聯(lián)不太的函數(shù),直接看最后句_dyld_objc_notify_register(&map_images, load_images, unmap_image);其中很明顯齿兔,load_images就是加載鏡像/加載模塊的意思橱脸,應(yīng)該是與我們?cè)掝}相關(guān)的參數(shù),點(diǎn)進(jìn)去看看它的實(shí)現(xiàn)

/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

蘋果對(duì)該函數(shù)官方給出的注釋是分苇,處理那些正在進(jìn)行映射的鏡像(images)的+load方法添诉。該方法的實(shí)現(xiàn)里面,做了兩件事情:

  • prepare_load_methods// Discover load methods -- 查找并準(zhǔn)備load方法医寿,以供后面去調(diào)用
  • call_load_methods();//Call +load methods -- 調(diào)用這些load方法

針對(duì)上面案例日志中出現(xiàn)的現(xiàn)象栏赴,先從結(jié)果出發(fā),逆向分析靖秩,來看看load方法是如何調(diào)用的须眷,進(jìn)入call_load_methods();的實(shí)現(xiàn)

/***********************************************************************
* call_load_methods

* Call all pending class and category +load methods.
調(diào)用所有的處理中的class和category的+load方法;

* Class +load methods are called superclass-first. 
class的+load方法會(huì)被先調(diào)用沟突,并且花颗,一個(gè)調(diào)用一個(gè)class的+load方法前,會(huì)先對(duì)其父類的+load進(jìn)行調(diào)用

* Category +load methods are not called until after the parent class's +load.
category的+load方法的調(diào)用惠拭,會(huì)發(fā)生在所有的class的+load方法完成調(diào)用之后扩劝。
* 
* This method must be RE-ENTRANT, because a +load could trigger 
* more image mapping. In addition, the superclass-first ordering 
* must be preserved in the face of re-entrant calls. Therefore, 
* only the OUTERMOST call of this function will do anything, and 
* that call will handle all loadable classes, even those generated 
* while it was running.
*
* The sequence below preserves +load ordering in the face of 
* image loading during a +load, and make sure that no 
* +load method is forgotten because it was added during 
* a +load call.

* Sequence:調(diào)用順序
* 1. Repeatedly call class +loads until there aren't any more
遍歷所有的class對(duì)象,調(diào)用它們的+load方法,知道所有class中的+load都完成了調(diào)用

* 2. Call category +loads ONCE.
調(diào)用所有category中的+load方法

* 3. Run more +loads if:
這里我還不太理解棒呛,感覺上面都已經(jīng)把所有的+load調(diào)用完了葡公,還不太理解哪里會(huì)產(chǎn)生新的+load方法。有待繼續(xù)補(bǔ)充......
*    (a) there are more classes to load, OR
*    (b) there are some potential category +loads that have 
*        still never been attempted.
* Category +loads are only run once to ensure "parent class first" 
* ordering, even if a category +load triggers a new loadable class 
* and a new loadable category attached to that class. 
*
* Locking: loadMethodLock must be held by the caller 
*   All other locks must not be held.
**********************************************************************/
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;
}

很明顯条霜,核心邏輯在do-while循環(huán)里面催什,循環(huán)中面做了兩件事:

  • 首先調(diào)用類對(duì)象的 +load方法--call_class_loads();,直到可加載的類的計(jì)數(shù)器減到0 --loadable_classes_used > 0
  • 然后調(diào)用分類的+load方法-- call_category_loads();//Call category +loads ONCE
小結(jié)A -- 程序啟動(dòng)之后宰睡,Runtime會(huì)在鏡像加載階段蒲凶,先調(diào)用所有類對(duì)象的+load方法,然后在調(diào)用所有分類的+load方法拆内,類對(duì)象與分類之間參與編譯順序旋圆,不會(huì)影響上面的結(jié)論。例如下圖的調(diào)試麸恍,注意編譯順序

這里產(chǎn)生了一個(gè)新的疑問:既然是方法調(diào)用灵巧,為什么category+load方法沒有“覆蓋”類對(duì)象的+load方法呢?

有關(guān)分類(category)中的方法對(duì)類對(duì)象中的同名方法產(chǎn)生的“覆蓋”現(xiàn)象如果還不太清楚,請(qǐng)參考我的Objective-C之Category的底層實(shí)現(xiàn)原理一文抹沪。

接著上面的源碼刻肄,繼續(xù)看看Runtime對(duì)于類對(duì)象和分類+load到底是如何調(diào)用的。我們先查看call_class_loads();融欧,這是對(duì)所有類對(duì)象(class)的+load方法的調(diào)用邏輯

/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;//首先用局部變量loadable_class保存loadable_classes列表
    int used = loadable_classes_used;//在用局部變量used保存loadable_classes_used
    loadable_classes = nil;//將loadable_classes置空
    loadable_classes_allocated = 0;//將loadable_classes_allocated清零
    loadable_classes_used = 0;//將loadable_classes_used清零
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {//遍歷classes列表
        Class cls = classes[i].cls;//從列表成員里面獲得cls
        load_method_t load_method = (load_method_t)classes[i].method;//從列表成員獲取對(duì)應(yīng)cls的+load 的IMP(方法實(shí)現(xiàn))
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);//這里就是對(duì)+load方法的調(diào)用敏弃,注意哦,這是直接的函數(shù)調(diào)用噪馏,不是消息機(jī)制那種哦麦到,這里跟類的方法列表什么沒關(guān)系,直接就是通過+load的IMP進(jìn)行調(diào)用了
    }
    // Destroy the detached list.
    if (classes) free(classes);
}

上面實(shí)現(xiàn)的主要邏輯發(fā)生在for循環(huán)里面欠肾,該for循環(huán)遍歷了一個(gè)叫classes的列表瓶颠,該列表存儲(chǔ)的是一堆loadable_class結(jié)構(gòu)體,loadable_class的定義如下

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

每一個(gè)struct loadable_class變量刺桃,存儲(chǔ)的應(yīng)該就是 一個(gè)類對(duì)象 + 一個(gè)與該類相關(guān)的方法實(shí)現(xiàn)粹淋。從loadable_class這個(gè)命名,說明它內(nèi)部的信息肯定是表示一個(gè)可以被加載的類的相關(guān)信息虏肾,因此合理推斷廓啊,它里面的method應(yīng)該就是類的+load方法欢搜,cls就是這個(gè)+load方法所對(duì)應(yīng)的類對(duì)象封豪。這個(gè)推斷是否正確,我們一會(huì)討論炒瘟。

我們?cè)倏纯丛创a中對(duì)于classes這個(gè)數(shù)組進(jìn)行遍歷時(shí)到底做了什么吹埠。很簡單,就是通過函數(shù)指針load_methodloadable_class中獲得+load方法的IMP作為其參數(shù),然后就直接對(duì)其進(jìn)行調(diào)用(*load_method)(cls, SEL_load);缘琅,所以粘都,類對(duì)象的+load方法的調(diào)用實(shí)際上就發(fā)生在這里。這里的for循環(huán)一旦結(jié)束刷袍,classes所包含的所有類對(duì)象的+load方法就會(huì)被依次調(diào)用翩隧,這跟一個(gè)類是否被在工程項(xiàng)目里被實(shí)例化過,是否接受過消息呻纹,沒有半毛錢關(guān)系堆生。

至此,Runtime對(duì)于+load方法是如何調(diào)用的問題我們分析了一半雷酪,弄清楚了類對(duì)象的+load方法的是怎么被一個(gè)一個(gè)調(diào)用的淑仆,也就是static void call_class_loads(void)這個(gè)函數(shù),接下來哥力,還有問題的另一半--static bool call_category_loads(void)蔗怠,也就是關(guān)于分類的+load方法的調(diào)用。進(jìn)入其中

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }
    return new_categories_added;
}

我們可以看到吩跋,這個(gè)方法的實(shí)現(xiàn)里面寞射,通過系統(tǒng)注釋,被劃分如下幾塊:

  • A -- // Detach current loadable list.分離可加載category列表锌钮,也就是把可加載列表的信息保存到本函數(shù)的局部變量cats數(shù)組上怠惶。
  • B -- // Call all +loads for the detached list.消費(fèi)cats里面的所有+load方法(也就是調(diào)用它們)
  • C -- // Compact detached list (order-preserving)清理cats里面已經(jīng)被消費(fèi)過的成員,并且更新used計(jì)數(shù)值
  • D -- // Copy any new +load candidates from the new list to the detached list.如果又出現(xiàn)了新的可加載的分類轧粟,將其相關(guān)內(nèi)容復(fù)制到cats列表上策治。
  • E -- // Destroy the new list.銷毀列表(這里指的是外部的loadable_categories變量)
  • F -- // Reattach the (now augmented) detached list. But if there's nothing left to load, destroy the list.更新幾個(gè)記錄了category+load信息的幾個(gè)全局變量。
    相比較于 call_class_loads方法兰吟,這里多了步驟C通惫、D、F混蔼。關(guān)于A履腋、B、E這三個(gè)步驟惭嚣,因?yàn)楦?code>call_class_loads方法里面實(shí)現(xiàn)是一樣的遵湖,不作重復(fù)解釋。且看看多出來的這幾步
    先看C

    對(duì)于這個(gè)晚吞,我畫個(gè)圖演示一下延旧,就明白了

    其實(shí)我感覺消費(fèi)完一輪+load方法之后,cats里面基本上會(huì)在這個(gè)步驟被清空槽地。

然后我們看看D步驟迁沫,如下圖


其實(shí)主要任務(wù)就是把新的可以加載的分類(category)信息(如果此時(shí)發(fā)現(xiàn)還有的話)添加到本函數(shù)的cats數(shù)組上芦瘾。

最后看看F步驟
小結(jié)B -- Runtime對(duì)于+load方法的調(diào)用,不是走的我們熟悉的“消息發(fā)送”路線集畅,而是直接拿到+load方法的IMP近弟,直接調(diào)用。因此不存在所謂“類的方法被category的方法覆蓋”的問題挺智,所以除了結(jié)論A的 類與分類的+load方法先后調(diào)用順序外祷愉,我們看到類與它的分類的所有的+load全部都被調(diào)用了,沒有被覆蓋赦颇。

目前谣辞,我們確定了類對(duì)象的+load方法會(huì)先于分類的+load方法被調(diào)用,并且不存在覆蓋現(xiàn)象沐扳。

  • 那么對(duì)于類于類之間+load調(diào)用順序是怎樣的泥从?
  • 同樣的疑問對(duì)于分類(category)又是如何呢?
    這兩個(gè)問題沪摄,我們就需要進(jìn)入prepare_load_methods方法的實(shí)現(xiàn)躯嫉,看看+load方法被調(diào)用前,Runtime是如何準(zhǔn)備它們的杨拐。
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        /** ??????????
         定制/規(guī)劃類的加載
         */
        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);
    }
}

上面的實(shí)現(xiàn)里祈餐,classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);可以看出,利用系統(tǒng)提供的函數(shù)_getObjc2NonlazyClassList哄陶,獲得類對(duì)象的列表帆阳,因?yàn)檫@是系統(tǒng)級(jí)別的函數(shù),應(yīng)該跟編譯過程的順序有關(guān)屋吨,這里先推測(cè)classlist中類的順序與類的編譯順序相同蜒谤。
接下來,就是遍歷classlist至扰,對(duì)其每個(gè)成員通過函數(shù)schedule_class_load()進(jìn)行處理

/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
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);

    /** <#注釋標(biāo)題#>??????????
     將cls添加到loadable_classes數(shù)組的最后面
     */
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

該函數(shù)里面就做兩件事:

  • 先遞歸調(diào)用自身(schedule_class_load())鳍徽,對(duì)當(dāng)前類(也就是函數(shù)傳入的參數(shù))的父類進(jìn)行處理
  • 處理完父類之后,將當(dāng)前類對(duì)象加入到可加載類的相關(guān)列表當(dāng)中 add_class_to_loadable_list(cls);

經(jīng)過這樣的整理之后敢课,最終整理過的裝載類對(duì)象相關(guān)信息的數(shù)組中阶祭,父類應(yīng)該排在子類前面。而不同的類對(duì)象之間在數(shù)組中的位置直秆,就可以參考它們.m的編譯順序來看了濒募。
每個(gè)類對(duì)象在被加入數(shù)組的時(shí)候,會(huì)通過cls->setInfo(RW_LOADED);設(shè)置標(biāo)簽標(biāo)記一下圾结,這樣瑰剃,如果該類下次被作為父類進(jìn)行遞歸調(diào)用的時(shí)候,就不會(huì)重復(fù)加入到列表中疫稿,保證一個(gè)類在數(shù)組中只出現(xiàn)一次培他。
最后再看一下add_class_to_loadable_list(cls);里面的邏輯


每個(gè)步驟的作用請(qǐng)看圖中的注釋鹃两。請(qǐng)注意其中一個(gè)細(xì)節(jié)遗座,第三句代碼method = cls->getLoadMethod();舀凛,進(jìn)一步查看一下這個(gè)getLoadMethod
拿到類的load方法

很明顯這個(gè)方法就是從一個(gè)類對(duì)象里面尋找load方法實(shí)現(xiàn),找到的話途蒋,就返回load方法的IMP猛遍,賦值給method懊烤。

然后把該類對(duì)象cls和對(duì)應(yīng)的+load方法IMP method賦值給loadable_classes列表最后一個(gè)成員腌紧,該成員我們前面篇章已經(jīng)說了壁肋,該成員就是loadable_class

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

我們之前推測(cè)的說loadable_class里面存放的IMP method;應(yīng)該就是+load方法的IMP浸遗,通過上面的分析跛锌,證明確實(shí)如此髓帽。

上面的是針對(duì)類對(duì)象的+load的方法所進(jìn)行的調(diào)用前的整理排布氢卡。下面我們看一下分類的+load方法是如何處理的译秦』魍耄回到prepare_load_methods方法,這里我直接貼出相關(guān)部分代碼截圖

對(duì)category的+load方法的準(zhǔn)備工作

可以看到,并沒像類一樣械拍,用一個(gè)schedule方法進(jìn)行遞歸處理,而是直接通過系統(tǒng)函數(shù)_getObjc2NonlazyCategoryList拿到分類的集合categorylist甲馋,因?yàn)閷?duì)分類來說定躏,不存在誰是誰的父類痊远,大家都是平級(jí)的碧聪,而且之前類對(duì)象的+load方法已經(jīng)處理過準(zhǔn)備好了逞姿,所以這里哼凯,只需將categorylist里面的分類對(duì)象一個(gè)一個(gè)拿出來断部,通過add_category_to_loadable_list方法處理好,一個(gè)一個(gè)加入到我們后面調(diào)用+load方法時(shí)所用的loadable_categories數(shù)組里面。add_category_to_loadable_list(cat)方法跟上面add_class_to_loadable_list(cls);方法里面的邏輯完全一致蔑祟,不做重復(fù)解讀趁耗。至此,+load方法的調(diào)用前的前期準(zhǔn)備工作疆虚,分析完了。

小結(jié)C
  • 那么對(duì)于類于類之間+load調(diào)用順序是怎樣的径簿?
    調(diào)用一個(gè)類對(duì)象的+load方法之前,會(huì)先調(diào)用其父類的+load方法(如果存在的話)篇亭,類與類之間缠捌,會(huì)按照編譯的順序译蒂,先后調(diào)用其+load方法曼月。一個(gè)類對(duì)象的+load方法不會(huì)被重復(fù)調(diào)用谊却,只可能被調(diào)用一次哑芹。
  • 同樣的疑問對(duì)于分類(category)又是如何呢炎辨?
    分類的+load方法蹦魔,會(huì)按照分類參與編譯的順序招盲,先編譯的,先被調(diào)用讳推。

我們?cè)谕ㄟ^代碼來驗(yàn)證一波顶籽。在開篇案例里面,我繼續(xù)添加幾個(gè)類和分類银觅,CLTeacherCLPerson子類)礼饱、CLTreeNSObject子類)、CLRiverNSObject子類)究驴、以及CLTeacher的兩個(gè)分類镊绪。

  • 首先看出,類對(duì)象的+load方法肯定是先與所有分類的+load方法被調(diào)用的洒忧。
  • 分類之間是按照編譯的順序蝴韭,先后調(diào)用+load
  • CLTeacher熙侍、CLTree榄鉴、CLRiver也是按照編譯的順序,先后調(diào)用+load蛉抓,由于CLPersonCLTeacher的父類庆尘,所以會(huì)先用它調(diào)用+load
    至此,完全和上面的小結(jié)C吻合芝雪。如果你有興趣减余,可以自己嘗試一下,變換一下源文件的編譯順序惩系,結(jié)果和這里的結(jié)論都是一致的位岔。

到這里如筛,關(guān)于+load方法調(diào)用的細(xì)節(jié)應(yīng)該就算分析完了~~~

PS:對(duì)于蘋果的源碼,我也在不斷的研讀和學(xué)習(xí)中抒抬,如果文中有闡述不對(duì)的地方杨刨,煩請(qǐng)告知指正,于此與各位共勉~~

參考來源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擦剑,一起剝皮案震驚了整個(gè)濱河市妖胀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惠勒,老刑警劉巖赚抡,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異纠屋,居然都是意外死亡涂臣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門售担,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赁遗,“玉大人,你說我怎么就攤上這事族铆⊙宜模” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵哥攘,是天一觀的道長剖煌。 經(jīng)常有香客問我,道長献丑,這世上最難降的妖魔是什么末捣? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮创橄,結(jié)果婚禮上箩做,老公的妹妹穿的比我還像新娘。我一直安慰自己妥畏,他們只是感情好邦邦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著醉蚁,像睡著了一般燃辖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上网棍,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天黔龟,我揣著相機(jī)與錄音,去河邊找鬼。 笑死氏身,一個(gè)胖子當(dāng)著我的面吹牛巍棱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛋欣,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼航徙,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了陷虎?” 一聲冷哼從身側(cè)響起到踏,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尚猿,沒想到半個(gè)月后窝稿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谊路,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年讹躯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了菩彬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缠劝。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖骗灶,靈堂內(nèi)的尸體忽然破棺而出惨恭,到底是詐尸還是另有隱情,我是刑警寧澤耙旦,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布脱羡,位于F島的核電站,受9級(jí)特大地震影響免都,放射性物質(zhì)發(fā)生泄漏锉罐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一绕娘、第九天 我趴在偏房一處隱蔽的房頂上張望脓规。 院中可真熱鬧,春花似錦险领、人聲如沸侨舆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挨下。三九已至,卻和暖如春脐湾,著一層夾襖步出監(jiān)牢的瞬間臭笆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愁铺,地道東北人凿菩。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓府寒,卻偏偏與公主長得像极颓,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子膀斋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354