應(yīng)用程序加載(六)-- 面試題load方法的調(diào)用順序

應(yīng)用程序加載(一) -- dyld流程分析
應(yīng)用程序加載(二) -- dyld&objc關(guān)聯(lián)以及類的加載初探
應(yīng)用程序加載(三)-- 類的加載
應(yīng)用程序加載(四)-- 分類的加載
應(yīng)用程序加載(五)-- 類擴(kuò)展和關(guān)聯(lián)對(duì)象


用一道面試題來終結(jié)應(yīng)用程序加載篇章。

面試題:類和分類中有同名方法篷扩,調(diào)用時(shí)會(huì)執(zhí)行哪個(gè)?

方法的調(diào)用就是底層消息發(fā)送(objc_msgSend)。最終會(huì)到方法列表中去查找整個(gè)方法的sel,由于分類的方法在類方法的前面舀透,所有會(huì)調(diào)用分類方法记某。

但是有一個(gè)方法,系統(tǒng)會(huì)在main之前自動(dòng)調(diào)用一次刁岸,就是+load方法。

而我們手動(dòng)調(diào)用load的時(shí)候她我,調(diào)用的是==分類==中的方法虹曙。

1.示例代碼

定義一個(gè)Person類和一個(gè)分類

//類
@interface Person : NSObject
@end
@implementation Person
+ (void)load {
    NSLog(@"%s", __func__);
}
@end

//分類
@interface Person (DZ)
@end
@implementation Person (DZ)
+ (void)load {
    NSLog(@"%s", __func__);
}
@end

//main中調(diào)用
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        NSLog(@"===手動(dòng)調(diào)用===");
        [Person load];
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

運(yùn)行結(jié)果:


  • load方法在main之前的系統(tǒng)調(diào)用中,先執(zhí)行的鸦难,后執(zhí)行分類
  • 在我們手動(dòng)調(diào)用的時(shí)候根吁,與普通方法相同,調(diào)用分類中的方法

接下來我們看看系統(tǒng)是合適調(diào)用的load方法合蔽,和是怎么調(diào)用的

2.源碼分析

load方法是在底層代碼的load_images中調(diào)用的

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // 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 發(fā)現(xiàn)load方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant) 調(diào)用load方法
    call_load_methods();
}
  • 之前的文章中講過load_images是如何調(diào)用的击敌,這里就不再多贅述了
  • 通過注釋,了解到拴事,先去發(fā)現(xiàn)load方法沃斤,然后去調(diào)用
2.1 發(fā)現(xiàn)load方法
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
    
    //非懶加載類
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    //非懶加載分類
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
  • 先處理非懶加載類,調(diào)用schedule_class_load函數(shù)
  • 在處理非攬件在分類刃宵,調(diào)用add_category_to_loadable_list函數(shù)

繼續(xù)看看這兩個(gè)函數(shù)中是如何處理的衡瓶。

2.1.1 非懶加載類
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); 
}
  • 通過傳入的參數(shù)cls, 然后遞歸優(yōu)先處理父類schedule_class_load(cls->superclass);
  • 緊接著調(diào)用add_class_to_loadable_list方法
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    //擴(kuò)容
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    //保存cls 和 cls中的方法
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • 這里就是對(duì)clsmethod進(jìn)行了存儲(chǔ)
2.1.2非懶加載分類
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
  • 代碼和類中類似牲证,也是進(jìn)行了存儲(chǔ)工作
2.2 調(diào)用load方法

“發(fā)現(xiàn)”的相關(guān)源碼看完了哮针,接著就該看看“調(diào)用”相關(guān)源碼

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;
}
  • 此處注釋寫的很清楚了,先執(zhí)行call_class_loads坦袍,調(diào)用類的load
  • 再執(zhí)行call_category_loads十厢,調(diào)用分類的load
2.2.1 類load調(diào)用
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());
        }
        //函數(shù)指針調(diào)用
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
  • 通過“發(fā)現(xiàn)”流程中保存的數(shù)據(jù),進(jìn)行循環(huán)處理
  • 注意一下捂齐,此時(shí)load方法的調(diào)用是通過函數(shù)指針的方法蛮放,而不是消息發(fā)送。
2.2.2 分類load調(diào)用
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, @selector(load));
            cats[i].cat = nil;
        }
    }
    
    //省略不關(guān)心的代碼
    ...
}
  • 與類中的處理類似
  • 分類中的load方法也是通過函數(shù)指針方式調(diào)用

3.手動(dòng)調(diào)用load

手動(dòng)調(diào)用load方法奠宜,是通過消息發(fā)送(objc_msgSend)的方式包颁。因此回去查找方法列表瞻想,優(yōu)先查到的是分類中的,所以調(diào)用的就是分類的load方法娩嚼。

可以通過斷點(diǎn)調(diào)試


然后打開匯編查看底層調(diào)用


總結(jié)

分類和類中有同名方法的時(shí)候

  1. 手動(dòng)調(diào)用方法蘑险,是進(jìn)行消息發(fā)送的方式,分類方法排在類的方法前面待锈,所以優(yōu)先調(diào)用分類中的方法
  2. 但是load方法漠其,在main之前嘴高,系統(tǒng)會(huì)調(diào)用一次竿音,調(diào)用順序是優(yōu)先調(diào)用中的load,然后調(diào)用分類中的load
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拴驮,一起剝皮案震驚了整個(gè)濱河市春瞬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌套啤,老刑警劉巖宽气,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異潜沦,居然都是意外死亡萄涯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門唆鸡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝影,“玉大人,你說我怎么就攤上這事争占∪悸撸” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵臂痕,是天一觀的道長(zhǎng)伯襟。 經(jīng)常有香客問我,道長(zhǎng)握童,這世上最難降的妖魔是什么姆怪? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮澡绩,結(jié)果婚禮上稽揭,老公的妹妹穿的比我還像新娘。我一直安慰自己英古,他們只是感情好淀衣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著召调,像睡著了一般膨桥。 火紅的嫁衣襯著肌膚如雪蛮浑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天只嚣,我揣著相機(jī)與錄音沮稚,去河邊找鬼。 笑死册舞,一個(gè)胖子當(dāng)著我的面吹牛蕴掏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播调鲸,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盛杰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了藐石?” 一聲冷哼從身側(cè)響起即供,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎于微,沒想到半個(gè)月后逗嫡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡株依,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年驱证,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恋腕。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抹锄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吗坚,到底是詐尸還是另有隱情祈远,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布商源,位于F島的核電站车份,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏牡彻。R本人自食惡果不足惜扫沼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庄吼。 院中可真熱鬧缎除,春花似錦、人聲如沸总寻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渐行。三九已至轰坊,卻和暖如春铸董,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肴沫。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工粟害, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颤芬。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓悲幅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親站蝠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汰具,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344