iOS消息轉(zhuǎn)發(fā)流程

一茫经、前言

一個類對象查找方法雪侥,我們都知道是先從緩存列表中去查找郎逃,然后在去方法列表里查找壤玫,這樣就能快速的查找到相關(guān)的imp,但是當我們沒有查找到相應(yīng)的imp時豁护,系統(tǒng)又會做一些什么事情呢?帶著這樣的好奇我們開始源碼的探究欲间,我們知道如果一個方法沒有實現(xiàn)楚里,運行時是會崩潰并且報錯;如下所示:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[LGPerson sayNB]: unrecognized selector sent to class 0x1000022a8'

二猎贴、動態(tài)方法解析

但是當我們在源碼的動態(tài)方法解析過程做相應(yīng)的事情時:程序就不會報錯班缎,可以繼續(xù)執(zhí)行并且進行我們想要的執(zhí)行結(jié)果;其中核心代碼來自resolveMethod_locked(inst, sel, cls, behavior);

if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}

1代碼調(diào)試

我們在項目中創(chuàng)建一個對象LGPerson 繼承自 NSObject,其中包括了幾個簡單的方法她渴;

@interface LGPerson : NSObject

- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;

+ (void)sayNB;
+ (void)lgClassMethod;

@end

而實現(xiàn)只包括以下幾個方法

- (void)sayHello{
    NSLog(@"%s",__func__);
}

- (void)sayNB{
    NSLog(@"%s",__func__);
}
- (void)sayMaster{
    NSLog(@"%s",__func__);
}


+ (void)lgClassMethod{
    NSLog(@"%s",__func__);
}

也就是說- (void)say666;+ (void)sayNB; 只有聲明达址。沒有實現(xiàn)。我們在main.m 文件中進行相關(guān)的方法調(diào)用趁耗,此時必然會出現(xiàn)文章開頭的崩潰現(xiàn)象沉唠,

LGPerson *person = [LGPerson alloc];
[person say666];

帶著問題的探索進入到動態(tài)方法解析的方法探索resolveInstanceMethodresolveClassMethod,看看究竟在動態(tài)解析過程中做了什么事情;
方法中我們只是簡單的打印了一句方法的名稱來到這里对粪;

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 來了",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
   
}

此時我們看到控制臺仍然會崩潰右冻,但是我們打印的方法日志會出現(xiàn)在崩潰之前装蓬,


動態(tài)方法日志打印.png

這里打印兩次著拭;也就是說這個動態(tài)方法解析的過程中resolveInstanceMethod 會調(diào)用兩次 ;為什么會走兩次接下來會分析牍帚;

圖片.png

根據(jù)條件判斷儡遮,我們看到當前的behavior = 3,而 LOOKUP_RESOLVER = 2 在根據(jù)計算slowpath(2) 進入我們的第一次動態(tài)方法解析;動態(tài)方法解析的源碼如下暗赶,由于我們是調(diào)用的對象方法鄙币,所以走圖中標注的地方,

圖片.png

動態(tài)方法的實現(xiàn)如下蹂随;

   runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // lookup resolveInstanceMethod
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    IMP imp = lookUpImpOrNil(inst, sel, cls);

所以第一次動態(tài)方法解析的sel == resolveInstanceMethod,然后將執(zhí)行后的imp返回十嘿,再次進行方法查找流程。所以此處會執(zhí)行兩次岳锁。

接著在resolveInstanceMethod中簡單的進行一個處理如下绩衷;

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 來了",NSStringFromSelector(sel));

    if (sel == @selector(say666)) {
        NSLog(@"%@ 來了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
   
}

此時就會出現(xiàn)一個神奇的現(xiàn)象。程序正常執(zhí)行。這就是動態(tài)方法解析的作用咳燕;

2 原理解析勿决;

動態(tài)方法解析的源碼截圖如上:分為兩種,一種是對象招盲,另一種是類低缩;

  • 1 如果是對象方法,則在當前類對象中查找曹货,因為對象方法就存儲在當前類對象的方法列表中
  • 2 如果是類方法咆繁,先從本類中查找是否有該類的imp,如果找到就直接返回,如果還是沒有找到顶籽,再對該類的元類,根元類么介,NSObject中查詢,如果有就返回蜕衡,沒有就進行消息轉(zhuǎn)發(fā)流程
  • 3 消息轉(zhuǎn)發(fā) 也分類兩種情況壤短,一種是快速轉(zhuǎn)發(fā),一種是慢速轉(zhuǎn)發(fā)流程慨仿;

三久脯、快速轉(zhuǎn)發(fā)流程

1 日志輔助

上面我們已經(jīng)知道了相關(guān)的消息處理流程,我們接下來分析一下相關(guān)的消息快速轉(zhuǎn)發(fā)流程镰吆,我們借助了instrumentObjcMessageSends 進行相關(guān)的日志跟蹤帘撰;代碼如下;

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *person = [LGPerson alloc];
        instrumentObjcMessageSends(YES);
        
        [person sayInstanceMethod];
        
        instrumentObjcMessageSends(NO);


        NSLog(@"Hello, World!");
    }
    return 0;
}

再次進入會到相應(yīng)的位置


日志地址.png

此時編譯項目我們找到相應(yīng)的日志文件;/tmp/msgSends 目錄下會多出一個文件;

圖片.png

打開文件州既,里邊有相應(yīng)的方法執(zhí)行信息窥淆;


日志打印.png

從以上我們能發(fā)現(xiàn)執(zhí)行的流程是resolveInstanceMethod ,再到forwardingTargetForSelector,繼而到methodSignatureForSelector,這就是以上的 動態(tài)方法解析 > 消息快速轉(zhuǎn)發(fā) > 消息慢速轉(zhuǎn)發(fā)的完整流程。

2 概念的介紹碎连;

以上得知我們先要進行消息的快速轉(zhuǎn)發(fā),也就是forwardingTargetForSelector 我們在開源的代碼中搜索并沒有找到相應(yīng)的定義。借此我們借助蘋果官方文檔進行查詢综苔;

Returns the object to which unrecognized messages should first be directed.
這就是蘋果官方的定義,也就是找到該方法的第一實現(xiàn)者位岔,從而就就結(jié)束了如筛,
所以此時我們

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [LGStudent alloc];
    
}

此時在運行代碼就不會出現(xiàn)崩潰的情況了,因為該方法內(nèi)已經(jīng)查詢到我們調(diào)用方法的實現(xiàn) 抒抬,也就是不管是不是我們自己方法接受者實現(xiàn)的杨刨,只要有這個方法的imp 程序就不會崩潰。

四擦剑,慢速轉(zhuǎn)發(fā)流程

如果快速查找流程沒有實現(xiàn)妖胀,那么就進入慢速查找流程的過程可免;那么就進入慢速轉(zhuǎn)發(fā)的流程;
methodSignatureForSelector,慢速轉(zhuǎn)發(fā)流程搭載forwardInvocation 來使用做粤;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    anInvocation.target = [LGStudent alloc];
    [anInvocation invoke];
    
}

此時我們同樣也能得到相應(yīng)的程序正常執(zhí)行流程浇借,這就是慢速轉(zhuǎn)發(fā)的過程,
通過NSInvocation 我們能看到相關(guān)的定義如下

圖片.png

所以此處我們不僅僅能修改target 還能監(jiān)控其他的屬性怕品;

圖片.png

通過控制臺我們能監(jiān)控相關(guān)的NSInvocation 屬性妇垢,從而動態(tài)的修改相關(guān)內(nèi)容也能起到慢速轉(zhuǎn)發(fā)的作用和目的。

五肉康、總結(jié)

以上就是對象調(diào)用方法的整套消息轉(zhuǎn)發(fā)流程闯估,從而實現(xiàn)消息轉(zhuǎn)發(fā),從創(chuàng)建對象吼和,到查找對象的isa,在進入類的bits_t,以及相關(guān)的類的結(jié)構(gòu)涨薪,緩存以及imp的查找,到最后的方法轉(zhuǎn)發(fā)機制炫乓,這就是一個對象在iOS開發(fā)系統(tǒng)的存在和意義刚夺,通過以上的相關(guān)學(xué)習(xí)和文章的整理,自己也對相關(guān)的消息機制進行一個深刻的認識末捣,這就是成長路上的一點小小的進步吧侠姑,繼續(xù)努力。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箩做,一起剝皮案震驚了整個濱河市莽红,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邦邦,老刑警劉巖安吁,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異燃辖,居然都是意外死亡鬼店,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門郭赐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薪韩,“玉大人,你說我怎么就攤上這事捌锭。” “怎么了罗捎?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵观谦,是天一觀的道長。 經(jīng)常有香客問我桨菜,道長豁状,這世上最難降的妖魔是什么捉偏? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮泻红,結(jié)果婚禮上夭禽,老公的妹妹穿的比我還像新娘。我一直安慰自己谊路,他們只是感情好讹躯,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缠劝,像睡著了一般潮梯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惨恭,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天秉馏,我揣著相機與錄音,去河邊找鬼脱羡。 笑死萝究,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的锉罐。 我是一名探鬼主播糊肤,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氓鄙!你這毒婦竟也來了馆揉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤抖拦,失蹤者是張志新(化名)和其女友劉穎升酣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體态罪,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡噩茄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了复颈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩聘。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耗啦,靈堂內(nèi)的尸體忽然破棺而出凿菩,到底是詐尸還是另有隱情,我是刑警寧澤帜讲,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布衅谷,位于F島的核電站,受9級特大地震影響似将,放射性物質(zhì)發(fā)生泄漏获黔。R本人自食惡果不足惜蚀苛,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玷氏。 院中可真熱鬧堵未,春花似錦、人聲如沸盏触。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耻陕。三九已至拙徽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诗宣,已是汗流浹背膘怕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留召庞,地道東北人岛心。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像篮灼,于是被迫代替她去往敵國和親忘古。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345