iOS:消息機(jī)制淺析

說(shuō)明 時(shí)間
首次發(fā)布 2017年08月09日
最近更新 2019年05月04日

消息機(jī)制可以認(rèn)為是objc_msgSend的執(zhí)行流程,包括消息發(fā)送阅悍、動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)致开。

一履怯、消息發(fā)送

消息發(fā)送

二、動(dòng)態(tài)方法解析

  • 檢查是否曾經(jīng)有動(dòng)態(tài)解析
    • 有:消息轉(zhuǎn)發(fā)
    • 沒(méi)有:調(diào)用 + (BOOL)resolveInstanceMethod:(SEL)sel+ (BOOL)resolveClassMethod:(SEL)sel 來(lái)動(dòng)態(tài)解析拼窥,并將標(biāo)記置為YES戏蔑。

注意:在動(dòng)態(tài)解析后,會(huì)重新回到 消息發(fā)送 的流程鲁纠。

示例:

//MZPerson.h
#import <Foundation/Foundation.h>

@interface MZPerson : NSObject

- (void)sayHello;
- (void)sayWorld;

+ (void)unimplementationClassMethod;

@end


//MZPerson.m
#import "MZPerson.h"
#import <objc/runtime.h>

@implementation MZPerson

/**
 動(dòng)態(tài)添加方法之成員方法
 */
- (void)addByMethod {
    NSLog(@"%@--%s--%s", self, sel_getName(_cmd), __func__);
}

void addByFunction(id self, SEL _cmd) {
    NSLog(@"%@--%s--%s", self, sel_getName(_cmd), __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    //通過(guò)C函數(shù)實(shí)現(xiàn)
    if (sel == @selector(sayHello)) {
        //types:類型編碼, v表示void, @表示id或NSObject*, :表示SEL. 這些都可以用@encode()查看
        class_addMethod(self, sel, (IMP)addByFunction, "v@:");
        
        //return YES/NO都是無(wú)所謂的,只是方便底層打印而已.但最好還是按規(guī)范來(lái),YES表示添加了方法到接受者
        return YES;
    }
    
    //通過(guò)OC方法實(shí)現(xiàn)
    if (sel == @selector(sayWorld)) {
        Method method = class_getInstanceMethod(self, @selector(addByMethod));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    //通過(guò)C函數(shù)實(shí)現(xiàn)
    if (sel == @selector(unimplementationClassMethod)) {
        //需要特別注意的是,類方法存放在元類里,所以這里要把方法添加到object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)addByFunction, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end

三总棵、消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)分為兩個(gè)階段:
第一階段:- (id)forwardingTargetForSelector:(SEL)aSelector
第二階段:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (void)forwardInvocation:(NSInvocation *)anInvocation改含;

當(dāng)階段一沒(méi)有實(shí)現(xiàn)情龄,或者返回nil的時(shí)候,則進(jìn)行階段二;如果消息轉(zhuǎn)發(fā)處理的是成員方法刃唤,那么三個(gè)階段都使用成員方法隔心,否則就使用類方法。如代碼里所示尚胞。

消息轉(zhuǎn)發(fā)圖示
//消息轉(zhuǎn)發(fā)第一階段
//底層是通過(guò)`id cls = object_getClass(receiver);`拿到receiver
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello)) {
//        //sayHello是對(duì)象方法,則返回的必須是一個(gè)對(duì)象,并且該對(duì)象中有成員方法sayHello
//        //返回nil,則直接進(jìn)入消息轉(zhuǎn)發(fā)的第二個(gè)階段
        return [GoodGuy new];
    }
    return [super forwardingTargetForSelector:aSelector];
}


//消息轉(zhuǎn)發(fā)第二階段(該部分是針對(duì)成員方法)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    if (aSelector == @selector(sayHello)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@q"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    //可以用anInvocation里的selector; 也可以自己直接指定一個(gè)方法
    anInvocation.selector = @selector(offerHelp:age:);

    NSString *name = @"小紅";
    NSInteger age = 18;

    //每一個(gè)方法會(huì)默認(rèn)隱藏兩個(gè)參數(shù):self硬霍、_cmd,所以自定義參數(shù)要從下標(biāo)2開(kāi)始
    [anInvocation setArgument:&name atIndex:2];
    [anInvocation setArgument:&age atIndex:3];

    GoodGuy *goodguy = [GoodGuy new];
    //做個(gè)判斷,保證指定的receiver會(huì)響應(yīng)該selector
    if ([goodguy respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:goodguy];
    }
}

//以下是針對(duì)于類方法的消息轉(zhuǎn)發(fā)(區(qū)別只是將這兩個(gè)方法換成`+`即可)
//消息轉(zhuǎn)發(fā)第一階段
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(unimplementationClassMethod)) {
        
        //返回nil,則直接進(jìn)入消息轉(zhuǎn)發(fā)的第二個(gè)階段
        //消息轉(zhuǎn)發(fā)類方法,這里需要傳入一個(gè)類對(duì)象
        return [GoodGuy class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    if (aSelector == @selector(unimplementationClassMethod)) {
        //如果需要傳參笼裳,則ObjCTypes一定要寫(xiě)寫(xiě)清唯卖,不然在傳參時(shí)會(huì)報(bào)越界
        //如果需要進(jìn)行消息轉(zhuǎn)發(fā)給一個(gè)`無(wú)參無(wú)返回值`的方法,無(wú)論被轉(zhuǎn)發(fā)的selector是什么類型的躬柬,通通可以用@"v@:"來(lái)表示
        return [NSMethodSignature signatureWithObjCTypes:"v@:@q"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {

    //可以用anInvocation里的selector; 也可以自己直接指定一個(gè)方法
    anInvocation.selector = @selector(offerHelp:age:);

    NSString *name = @"小紅";
    NSInteger age = 18;

    //每一個(gè)方法會(huì)默認(rèn)隱藏兩個(gè)參數(shù):self拜轨、_cmd,所以自定義參數(shù)要從下標(biāo)2開(kāi)始
    [anInvocation setArgument:&name atIndex:2];
    [anInvocation setArgument:&age atIndex:3];

    GoodGuy *goodguy = [GoodGuy new];
    //做個(gè)判斷,保證指定的receiver會(huì)響應(yīng)該selector
    if ([goodguy respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:goodguy];
    }
}

拓展

消息轉(zhuǎn)發(fā)還可以用于降低 unrecognized selector 的崩潰率。

示例:

#import <Foundation/Foundation.h>

@interface MZPerson : NSObject

- (void)printPerson:(NSString *)name age:(NSInteger)age;

@end

@implementation MZPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 能響應(yīng)的直接走原先的邏輯
    if ([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    }
    
    // 找不到的方法
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 找不到的方法允青,都會(huì)來(lái)到這里
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MZPerson *person = [[MZPerson alloc] init];
        //MZPerson里并沒(méi)有實(shí)現(xiàn)`printPerson:age:`的方法
        [person printPerson:@"LynnZhang" age:12];
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載橄碾,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末颠锉,一起剝皮案震驚了整個(gè)濱河市法牲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琼掠,老刑警劉巖拒垃,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瓷蛙,居然都是意外死亡悼瓮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門艰猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)横堡,“玉大人,你說(shuō)我怎么就攤上這事姥宝〕嵊” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵腊满,是天一觀的道長(zhǎng)套么。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碳蛋,這世上最難降的妖魔是什么胚泌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮肃弟,結(jié)果婚禮上玷室,老公的妹妹穿的比我還像新娘零蓉。我一直安慰自己,他們只是感情好穷缤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布敌蜂。 她就那樣靜靜地躺著,像睡著了一般津肛。 火紅的嫁衣襯著肌膚如雪章喉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天身坐,我揣著相機(jī)與錄音秸脱,去河邊找鬼。 笑死部蛇,一個(gè)胖子當(dāng)著我的面吹牛摊唇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涯鲁,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼巷查,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撮竿?” 一聲冷哼從身側(cè)響起吮便,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤笔呀,失蹤者是張志新(化名)和其女友劉穎幢踏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體许师,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡房蝉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了微渠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搭幻。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逞盆,靈堂內(nèi)的尸體忽然破棺而出檀蹋,到底是詐尸還是另有隱情,我是刑警寧澤云芦,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布俯逾,位于F島的核電站,受9級(jí)特大地震影響舅逸,放射性物質(zhì)發(fā)生泄漏桌肴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一琉历、第九天 我趴在偏房一處隱蔽的房頂上張望坠七。 院中可真熱鬧水醋,春花似錦、人聲如沸彪置。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拳魁。三九已至宫蛆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間的猛,已是汗流浹背耀盗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卦尊,地道東北人叛拷。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像岂却,于是被迫代替她去往敵國(guó)和親忿薇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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