iOS Cateogry的深入理解&&load方法調(diào)用&&分類重寫方法的調(diào)用順序(一)

首先先看幾個(gè)面試問題

  • Cateogry里面有l(wèi)oad方法么? load方法什么時(shí)候調(diào)用?load方法有繼承么?

1. 新建一個(gè)項(xiàng)目,并添加TCPerson類,并給TCPerson添加兩個(gè)分類

類結(jié)構(gòu)

2.新建一個(gè)TCStudent類繼承自TCPerson,并且給TCStudent也添加兩個(gè)分類

Cateogry里面有l(wèi)oad方法么?

  • 答:分類里面肯定有l(wèi)oad

#import "TCPerson.h"

@implementation TCPerson
+ (void)load{
    
}
@end
#import "TCPerson+TCtest1.h"

@implementation TCPerson (TCtest1)
+ (void)load{
    
}
@end
#import "TCPerson+TCTest2.h"

@implementation TCPerson (TCTest2)
+ (void)load{
    
}
@end

load方法什么時(shí)候調(diào)用?

  • load方法在runtime加載類和分類的時(shí)候調(diào)用load

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}

@implementation TCPerson
+ (void)load{
    NSLog(@"TCPerson +load");
}
@end


@implementation TCPerson (TCtest1)
+ (void)load{
    NSLog(@"TCPerson (TCtest1) +load");
}
@end
@implementation TCPerson (TCTest2)
+ (void)load{
    NSLog(@"TCPerson (TCtest2) +load");
}
@end

可以看到我們在main里面不導(dǎo)入任何的頭文件,也不引用任何的類,直接運(yùn)行,控制臺(tái)輸出結(jié)果:


輸出結(jié)果

從輸出結(jié)果我們可以看出,三個(gè)load方法都被調(diào)用

問題:分類重寫方法,真的是覆蓋原有類的方法么?如果不是,到底分類的方法是怎么調(diào)用的?

  • 首先我們在TCPerson申明一個(gè)方法+ (void)test并且在它的兩個(gè)分類都重寫+ (void)test

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TCPerson : NSObject
+ (void)test;
@end

NS_ASSUME_NONNULL_END

#import "TCPerson.h"

@implementation TCPerson
+ (void)load{
    NSLog(@"TCPerson +load");
}
+ (void)test{
    NSLog(@"TCPerson +test");
}
@end

分類重寫test

#import "TCPerson+TCtest1.h"

@implementation TCPerson (TCtest1)
+ (void)load{
    NSLog(@"TCPerson (TCtest1) +load");
}
+ (void)test{
    NSLog(@"TCPerson (TCtest1) +test1");
}
@end
#import "TCPerson+TCTest2.h"

@implementation TCPerson (TCTest2)
+ (void)load{
    NSLog(@"TCPerson (TCtest2) +load");
}
+ (void)test{
    NSLog(@"TCPerson (TCtest2) +test2");
}
@end

在main里面我們調(diào)用test

#import <Foundation/Foundation.h>
#import "TCPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [TCPerson test];
    }
    return 0;
}

輸出結(jié)果:

輸出結(jié)果

從輸出結(jié)果中我們可以看到,只有分類2中的test被調(diào)用,為什么只調(diào)用分類2中的test了?


編譯順序

因?yàn)榫幾g順序是分類2在后,1在前,這個(gè)時(shí)候我們改變編譯順序(拖動(dòng)文件就行了)


改變后的順序

其輸出結(jié)果為:
image.png

細(xì)心的老鐵會(huì)看到,為什么load方法一直都在調(diào)用,這是為什么了?它和test方法到底有什么不同了?真的是我們理解中的load不覆蓋,test覆蓋了,所以才出現(xiàn)這種情況么?

我們打印TCPerson的類方法

void printMethodNamesOfClass(Class cls)
{
    unsigned int count;
    // 獲得方法數(shù)組
    Method *methodList = class_copyMethodList(cls, &count);
    
    // 存儲(chǔ)方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍歷所有的方法
    for (int i = 0; i < count; i++) {
        // 獲得方法
        Method method = methodList[I];
        // 獲得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    
    // 釋放
    free(methodList);
    
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [TCPerson test];
        printMethodNamesOfClass(object_getClass([TCPerson class]));
    }
    return 0;
}

輸出結(jié)果:
打印

可以看到,TCPerson的所有類方法名,并不是覆蓋,三個(gè)load,三個(gè)test,方法都在

load源碼分析:查看objc底層源碼我們可以看到:

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

load方法它是先調(diào)用 while (loadable_classes_used > 0) {call_class_loads(); }類的load,再調(diào)用more_categories = call_category_loads()分類的load,和編譯順序無關(guān),都會(huì)調(diào)用
我們查看call_class_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(classes);
}

其通過的是load_method_t函數(shù)指針直接調(diào)用
函數(shù)指針直接調(diào)用

typedef void(*load_method_t)(id, SEL);

其分類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, SEL_load);
            cats[i].cat = nil;
        }
    }

為什么test不一樣了

因?yàn)閠est是因?yàn)橄C(jī)制調(diào)用的,objc_msgSend([TCPerson class], @selector(test));消息機(jī)制就牽扯到了isa方法查找,test在元類方法里面順序查找的(關(guān)于isa,可以查看我的實(shí)例對(duì)象,類對(duì)象,元類對(duì)象的關(guān)聯(lián)---isa/superclass指針(2))里面有詳細(xì)的關(guān)于test的方法調(diào)用原理

load只在加載類的時(shí)候調(diào)用一次,且先調(diào)用類的load,再調(diào)用分類的

load的繼承關(guān)系調(diào)用
首先我們先看TCStudent

#import "TCStudent.h"

@implementation TCStudent

@end

不寫load方法調(diào)用


TCStudent不寫load

TCStudent寫上load


TCStudent寫上load

從中可以看出子類不寫load的方法,調(diào)用父類的load,當(dāng)子類調(diào)用load時(shí),先調(diào)用父類的load,再調(diào)用子類的load,父類子類load取決于你寫load方法沒有,如果都寫了,先調(diào)用父類的,再調(diào)用子類的

總結(jié):先調(diào)用類的load,如果有子類,則先看子類是否寫了load,如果寫了,則先調(diào)用父類的load,再調(diào)用子類的load,當(dāng)類子類調(diào)用完了,再是分類,分類的load取決于編譯順序,先編譯,則先調(diào)用,test的方法調(diào)用走的是消息發(fā)送機(jī)制,其底層原理和load方法有著本質(zhì)的區(qū)別,消息發(fā)送主要取決于isa的方法查找順序

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容