iOS消息轉(zhuǎn)發(fā)機(jī)制

IMG_1160.JPG

函數(shù)的調(diào)用方式

Objective-C是C語(yǔ)言的超集褥蚯,C語(yǔ)言的函數(shù)調(diào)用方式是“靜態(tài)綁定的”挚冤,也就是說(shuō)在編譯的時(shí)候就知道運(yùn)行時(shí)要調(diào)用什么函數(shù),如果調(diào)用一個(gè)未聲明的函數(shù)赞庶,編譯期間就會(huì)報(bào)錯(cuò)训挡。

void printGoodMorning(){
    printf("good morning,huang");
}

void printGoodAfternoon(){
    prinf("good afternoon,huang");
}

void helloHuang(int time){
    if(time > 6 && time < 12){
        printGoodMorning();
    }else if(time < 18){
        printGoodAfternoon();
    }else{
        printHelloAllTheTime();//error here
    }
}

但是如果我們把上面這段代碼改寫(xiě)一下,如下

void printGoodMorning(){
    printf("good morning,huang");
}

void printGoodAfternoon(){
    prinf("good afternoon,huang");
}

void helloHuang(int time){
    void (*hello)();
    if(time > 6 && time < 12){
        hello = printGoodMorning;
    }else if(time < 18){
        hello = printGoodAfternoon;
    }
    
    hello()
}

你會(huì)發(fā)現(xiàn)只有當(dāng)程序運(yùn)行起來(lái)之后尘执,才能知道要執(zhí)行哪個(gè)函數(shù)舍哄,這就得使用“動(dòng)態(tài)綁定”的概念了

Objective-C是使用傳遞消息的機(jī)制來(lái)調(diào)用函數(shù),這就會(huì)使用到動(dòng)態(tài)綁定的機(jī)制在運(yùn)行期來(lái)決定到底調(diào)用哪個(gè)方法誊锭,甚至我們可以在運(yùn)行時(shí)改變對(duì)象調(diào)用的方法表悬。

對(duì)象發(fā)送消息大概是這樣的:

id returnType = [object messageName:parameter];

編譯器會(huì)轉(zhuǎn)化為我們斷點(diǎn)堆棧中經(jīng)常能看到的下面這樣的C函數(shù):

void objc_msgSend(id self, SEL cmd,...)

這是個(gè)參數(shù)可以變化的函數(shù),第一個(gè)參數(shù)代表了消息的接收者丧靡,第二個(gè)參數(shù)是所需要執(zhí)行的方法名蟆沫,后面是消息的參數(shù)籽暇。

objc_msgSend是如何依據(jù)消息的接收者和方法名找到合適的方法呢?

首先饭庞,需要在接收者所屬的類(lèi)中的方法列表中去查找這個(gè)方法戒悠,找到了直接調(diào)用,沒(méi)找到則一級(jí)一級(jí)便利去基類(lèi)的方法列表舟山,直到NSObject绸狐。如果沒(méi)找到,那么就會(huì)觸發(fā)“消息轉(zhuǎn)發(fā)”機(jī)制累盗。

消息轉(zhuǎn)發(fā)機(jī)制如果處理妥當(dāng)寒矿,那么這條消息則能正常處理,如果處理不善若债,或者根本沒(méi)處理符相,那么就會(huì)看到我們常看到的崩潰:

-[xxxobject xxxfunc]: unrecognized selector sent to instance 0xxx

簡(jiǎn)單的解決方案就是蠢琳,去對(duì)應(yīng)的類(lèi)添加對(duì)應(yīng)的方法就行了啊终。但是現(xiàn)在我們要提到的是,怎么動(dòng)態(tài)的使用消息轉(zhuǎn)發(fā)來(lái)處理這個(gè)問(wèn)題傲须。

消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)有下面幾個(gè)階段

  • “動(dòng)態(tài)方法解析”蓝牲,征詢接收者所屬的類(lèi),是否需要?jiǎng)討B(tài)添加方法躏碳,來(lái)處理這個(gè)未找到的方法搞旭。
  • “備用接受者”,轉(zhuǎn)發(fā)給其他的對(duì)象處理這個(gè)方法菇绵。
  • “完整的消息轉(zhuǎn)發(fā)機(jī)制”肄渗,如果未聲明其他對(duì)象處理,或者其他對(duì)象處理失敗了咬最,那么系統(tǒng)就會(huì)把消息所有相關(guān)的封裝成一個(gè)NSInvocation對(duì)象翎嫡,我們可以拿到這個(gè)NSInvocation對(duì)象,addTarget指明方法的處理者永乌。

下面我們通過(guò)一個(gè)demo來(lái)理解下消息轉(zhuǎn)發(fā)
新建一個(gè)Person的類(lèi)

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
- (instancetype)init:(NSString *)name;
- (void)eat;
- (void)sleep;
@end

這個(gè)類(lèi)有一個(gè)init方法和兩個(gè)實(shí)例方法惑申,對(duì)應(yīng).m都有實(shí)現(xiàn)。

#import "Person.h"
@implementation Person
- (instancetype)init:(NSString *)name{
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}

- (void)eat{
    NSLog(@"i love fish");
}

- (void)sleep{
    NSLog(@"good night");
}
@end

現(xiàn)在我們?cè)賱?chuàng)建一個(gè)Developer的類(lèi)繼承于Person

#import "Person.h"

@interface Developer : Person
- (void)developerCoding;
- (void)developerDebug;
@end

.m里面實(shí)現(xiàn)了developer前綴的兩個(gè)方法

#import "Developer.h"

@implementation Developer
- (instancetype)init:(NSString *)name{
    self =  [super init:name];
    if (self) {
        
    }
    
    return self;
}

- (void)developerCoding{
    NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
    NSLog(@"i hate pm");
}

- (void)developerDebug{
    NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
    NSLog(@"i hate bug");
}
@end

這個(gè)時(shí)候翅雏,我們創(chuàng)建一個(gè)Person的實(shí)例對(duì)象圈驼,并讓它做一些事情

Person *person = [[Person alloc] init:@"yuan"];
[person eat];
[person sleep];
[person performSelector:@selector(developerCoding)];
[person performSelector:@selector(developerDebug)];

本來(lái)這個(gè)人實(shí)例吃吃睡睡挺滋潤(rùn)的,但是為了不荒廢人生望几,還是該干點(diǎn)活的绩脆,所以除了吃和睡啦逆,強(qiáng)制讓他去寫(xiě)代碼改bug恕洲。

但是你會(huì)發(fā)現(xiàn)屈梁,強(qiáng)制他去做他不會(huì)的活盐碱,他就要革命了(crash)。在不做任何處理的情況下玉锌,這個(gè)人根本完成不了編程和解bug的活名挥。所以作為組織領(lǐng)導(dǎo),我們要教導(dǎo)下面的人主守,接到完不成的工作禀倔,應(yīng)該積極響應(yīng),而不是搞什么革命参淫。

所以某位同志做不了這個(gè)活的時(shí)候蹋艺,我們教導(dǎo)他說(shuō):你應(yīng)該這樣做。

首先黄刚,當(dāng)發(fā)現(xiàn)不能干這個(gè)活,那么你可以現(xiàn)學(xué)呀民效。

//消息轉(zhuǎn)發(fā)第一步 動(dòng)態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selName = NSStringFromSelector(sel);
    if ([selName hasPrefix:@"developer"]) {
        class_addMethod(self, sel, (IMP)shouldDoSomeThing, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//動(dòng)態(tài)將實(shí)現(xiàn)轉(zhuǎn)到這個(gè)函數(shù)
void shouldDoSomeThing(id self ,SEL _cmd){
    NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
    NSLog(@"maybe i should do something like a developer");
}

給類(lèi)動(dòng)態(tài)創(chuàng)建一個(gè)方法實(shí)現(xiàn)憔维,然后處理這個(gè)原本無(wú)法響應(yīng)的方法。這步完成畏邢,消息得到處理业扒,結(jié)束。

如果你是個(gè)懶人舒萎,不愿意學(xué)這個(gè)編程程储,那怎么辦呢,那學(xué)會(huì)甩鍋呀臂寝。

//消息轉(zhuǎn)發(fā)第二步 備選接收者
- (id)forwardingTargetForSelector:(SEL)aSelector{
    Developer *dev = [[Developer alloc] init:@"Huang"];
    if ([dev respondsToSelector:aSelector]) {
        return dev;
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

去找了一個(gè)專(zhuān)業(yè)的開(kāi)發(fā)人員章鲤,來(lái)處理這個(gè)這個(gè)問(wèn)題,消息被轉(zhuǎn)發(fā)給了Developer的實(shí)例咆贬,得到了處理败徊,結(jié)束。

如果既不動(dòng)態(tài)生成方法實(shí)現(xiàn)掏缎,也不轉(zhuǎn)發(fā)給能處理的備選接收者皱蹦,最后還有一個(gè)解決方案。那你寫(xiě)一個(gè)方法的詳細(xì)報(bào)告眷蜈,對(duì)組織說(shuō)明這個(gè)活的詳細(xì)細(xì)節(jié)是什么沪哺,上交這份報(bào)告(NSInvocation)給組織,組織給你處理酌儒。

//消息轉(zhuǎn)發(fā)第三部 完整的消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([Developer instancesRespondToSelector:anInvocation.selector]) {
        Developer *dev = [[Developer alloc] init:@"Huang"];
        [anInvocation invokeWithTarget:dev];
    }
}

//person找不到developer相關(guān)的方法辜妓,就是因?yàn)檫@個(gè)函數(shù)找不到方法的實(shí)現(xiàn)簽名引發(fā)了崩潰,我們這里需要給aSelector新建方法簽名 再交給Developer對(duì)象去處理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        //不簽名會(huì)崩潰
        //判斷實(shí)現(xiàn)類(lèi)的實(shí)例是否有這個(gè)方法 有則簽名這個(gè)方法 保證能正確轉(zhuǎn)發(fā)
        if ([Developer instancesRespondToSelector:aSelector]) {
            signature = [Developer instanceMethodSignatureForSelector:aSelector];
        }
        //直接簽名
//        if ([NSStringFromSelector(aSelector) hasPrefix:@"developer"]) {
//            signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
//        }
    }
    
    return signature;
}

組織要處理呀,不處理就革命了嫌拣。拿到方法的細(xì)節(jié)封裝NSInvocation對(duì)象之前柔袁,組織需要知道為啥會(huì)上傳報(bào)告,通過(guò)methodSignatureForSelector异逐,發(fā)現(xiàn)組織的科技樹(shù)(方法簽名)里面沒(méi)有developer相關(guān)的技能捶索,所以干不了這個(gè)活。那么既然科技樹(shù)里面沒(méi)有developer相關(guān)技能灰瞻,那么我們就要先點(diǎn)亮科技樹(shù)才行腥例,所以我們給developer前綴的方法進(jìn)行方法簽名。點(diǎn)亮科技樹(shù)酝润。

然后forwardInvocation中燎竖,把報(bào)告交給能干這個(gè)活的人,完成消息轉(zhuǎn)發(fā)要销,結(jié)束构回。

根據(jù)實(shí)際的情況決定怎么處理NSInvocation對(duì)象,本Demo只是簡(jiǎn)單地把NSInvocation交給了備選接收者處理疏咐。

總結(jié)

  • 如果一個(gè)對(duì)象無(wú)法響應(yīng)一個(gè)方法纤掸,那么就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制
  • 第一步,我們可以動(dòng)態(tài)創(chuàng)建一個(gè)方法實(shí)現(xiàn)去響應(yīng)這個(gè)消息浑塞,消息轉(zhuǎn)發(fā)結(jié)束
  • 第一步未處理借跪,第二步我們可以選擇一個(gè)備選的消息接收者去處理這個(gè)消息
  • 第二步未實(shí)現(xiàn),最后一步第三步酌壕,啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制掏愁,處理方法簽名,實(shí)現(xiàn)NSInvocation對(duì)象轉(zhuǎn)發(fā)卵牍。
  • 最好在第一步處理果港,次之第二步,如果第三步只是使用備選接收者處理辽慕,還不如直接第二步快速處理結(jié)束京腥,畢竟第三步需要方法簽名和封裝NSInvocation。

Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溅蛉,一起剝皮案震驚了整個(gè)濱河市公浪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌船侧,老刑警劉巖欠气,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镜撩,居然都是意外死亡预柒,警方通過(guò)查閱死者的電腦和手機(jī)队塘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宜鸯,“玉大人憔古,你說(shuō)我怎么就攤上這事×苄洌” “怎么了鸿市?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)即碗。 經(jīng)常有香客問(wèn)我焰情,道長(zhǎng),這世上最難降的妖魔是什么剥懒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任内舟,我火速辦了婚禮,結(jié)果婚禮上初橘,老公的妹妹穿的比我還像新娘验游。我一直安慰自己,他們只是感情好保檐,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布批狱。 她就那樣靜靜地躺著,像睡著了一般展东。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炒俱,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天盐肃,我揣著相機(jī)與錄音,去河邊找鬼权悟。 笑死砸王,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峦阁。 我是一名探鬼主播谦铃,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼榔昔!你這毒婦竟也來(lái)了驹闰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撒会,失蹤者是張志新(化名)和其女友劉穎嘹朗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體诵肛,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屹培,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褪秀。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓄诽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出媒吗,到底是詐尸還是另有隱情仑氛,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布蝴猪,位于F島的核電站调衰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏自阱。R本人自食惡果不足惜嚎莉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沛豌。 院中可真熱鬧趋箩,春花似錦、人聲如沸加派。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芍锦。三九已至竹勉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娄琉,已是汗流浹背次乓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留孽水,地道東北人票腰。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像女气,于是被迫代替她去往敵國(guó)和親杏慰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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