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

前言

今天我們再來通過另外一個(gè)機(jī)制來感受一下OC的動(dòng)態(tài)特性吧,那就是OC的消息轉(zhuǎn)發(fā)機(jī)制

在之前的不一樣的OC中我們有提到,OC是消息型語言,OC中的方法調(diào)用其實(shí)只是傳遞消息而已,編譯器并不能決定程序真正執(zhí)行的到底是哪段代碼,這個(gè)工作,需要運(yùn)行時(shí)系統(tǒng)來完成.

那我們今天要討論的消息轉(zhuǎn)發(fā)又是什么呢?

既然OC的方法調(diào)用實(shí)際上都是消息傳遞,那傳遞的消息內(nèi)容和時(shí)機(jī)其實(shí)編譯器并不能完全控制,這時(shí)我們是不是能夠聯(lián)想到這樣的場景:一個(gè)對象接受了一個(gè)編譯器控制外的消息,這個(gè)對象對這個(gè)消息無從下手,他并不具備解決這個(gè)消息的能力(沒有實(shí)現(xiàn)相應(yīng)方法),這個(gè)時(shí)候該怎么辦呢?

當(dāng)遇到這種情況的時(shí)候,我們的消息轉(zhuǎn)發(fā)機(jī)制就被觸發(fā)了,系統(tǒng)將利用這套機(jī)制竭力幫我們控制這個(gè)糟糕的局面,不至于讓程序崩潰,但是如果消息轉(zhuǎn)發(fā)機(jī)制竭盡全力通過各種辦法也無法解決這個(gè)意料之外的消息時(shí),那么程序就會崩潰,并產(chǎn)生那個(gè)經(jīng)典的崩潰日志:


unrecognized selector sent to instance 0x60000000b480

消息轉(zhuǎn)發(fā)機(jī)制的三部曲

消息轉(zhuǎn)發(fā)機(jī)制在極力為我們收拾殘局的時(shí)候,會有三步措施,如果在最壞的情況下,這三個(gè)步驟將會按順序依次執(zhí)行,如果這三個(gè)步驟都無法挽回,那么程序?qū)罎?值得一提的是:如果消息轉(zhuǎn)發(fā)機(jī)制的某一個(gè)步驟就能解決這個(gè)問題,那么下一個(gè)步驟就不會執(zhí)行了,消息轉(zhuǎn)發(fā)機(jī)制就到此為止了(事情都解決了,當(dāng)然不需要下一步了)

我們現(xiàn)在用一個(gè)簡單的例子來觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制,并利用消息轉(zhuǎn)發(fā)機(jī)制解決問題

我新建了一個(gè)叫做Person的類,并在它的.h文件里為Person聲明了一個(gè)實(shí)例方法:run

以下是.h文件的內(nèi)容.


#import <Foundation/Foundation.h>

@interface Person : NSObject

-(void)run;

@end

大家猜猜我在.m文件里做了什么?以下是.m文件的內(nèi)容:

#import "Person.h"

@implementation Person

@end

哈哈,我什么都沒做,甚至沒有對.h中的run方法寫實(shí)現(xiàn).

我為什么要這么做呢?這樣我們就可以欺騙編譯器,在調(diào)用這個(gè)方法的時(shí)候,編譯器默認(rèn)我們已經(jīng)對這個(gè)方法寫了實(shí)現(xiàn),他就會讓我們舒舒服服的調(diào)用這個(gè)方法,并不會報(bào)錯(cuò),組織我們隊(duì)這個(gè)方法的調(diào)用

那接下來,我們在控制器中新建一個(gè)person對象,并調(diào)用這個(gè)方法的run方法

Person *p1=[Person new];
    
    [p1 run];

大家應(yīng)該猜的到會發(fā)生什么了吧,我們能夠成功編譯并運(yùn)行這個(gè)程序,但是當(dāng)代碼執(zhí)行到了run方法時(shí),程序崩潰了,并報(bào)了我們常見的那個(gè)錯(cuò)誤unrecognized selector sent to instance 0x60000000b480

大家一定想知道這其中到底發(fā)生了什么事情:當(dāng)run方法被調(diào)用時(shí),運(yùn)行時(shí)系統(tǒng)就會對p1這個(gè)對象發(fā)送一條run的消息,意料之中的是p1這個(gè)對象并不能對相應(yīng)這個(gè)消息,因?yàn)槲覀儧]有寫run的實(shí)現(xiàn)方法,那接下來就會觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制了,那既然觸發(fā)了消息轉(zhuǎn)發(fā)機(jī)制,為什么還是崩潰了呢,因?yàn)?這就是那種最壞的情況,消息轉(zhuǎn)發(fā)機(jī)制三個(gè)步驟依次執(zhí)行卻依然沒有挽回崩潰的結(jié)果.

那么我們快看一看怎么讓消息轉(zhuǎn)發(fā)機(jī)制發(fā)揮作用吧.

步驟一:能不能動(dòng)態(tài)添加一個(gè)函數(shù)解決這個(gè)消息啊

在這一步中,面對無法解決的消息,消息轉(zhuǎn)發(fā)機(jī)制首先做的是,看看能否動(dòng)態(tài)添加一個(gè)方法實(shí)現(xiàn),來解決這個(gè)消息.

如果是實(shí)例方法,系統(tǒng)會調(diào)用這個(gè)這個(gè)實(shí)例所屬類的類方法:

+(BOOL)resolveInstanceMethod:(SEL)sel

如果是類方法:

+(BOOL)resolveClassMethod:(SEL)sel

這對方法如何使用呢?我們來看:

我在person類的.m文件中寫了如下代碼:

+(BOOL)resolveInstanceMethod:(SEL)sel
{

    if (sel==@selector(run)) {
        class_addMethod(self, sel, (IMP)run , "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void run (id self,SEL _cmd)
{
    NSLog(@"fdfsfaf");
}

我們寫了一個(gè)run的c語言函數(shù),當(dāng)消息機(jī)制觸發(fā)時(shí),selector為run時(shí)那么就會利用運(yùn)行時(shí)庫動(dòng)態(tài)添加一個(gè)方法,而這個(gè)方法的函數(shù)實(shí)現(xiàn)是我們用C寫的一個(gè)函數(shù),這個(gè)函數(shù)的兩個(gè)參數(shù)為self,和_cmd,因?yàn)镺C方法的本質(zhì)就是至少包含兩個(gè)參數(shù)的C函數(shù)這兩個(gè)參數(shù)便是隱藏的self,和_cmd,前者是消息接受者,后者是一個(gè)SEL指針

通過這樣的操作,系統(tǒng)就為我們通過動(dòng)態(tài)添加一個(gè)方法的方式來解決了這個(gè)不能響應(yīng)的消息
這也是消息轉(zhuǎn)發(fā)機(jī)制的第一步,如果這一步并沒有解決這個(gè)問題,那便要有下面的步驟了.

步驟二:能不能找別人處理這個(gè)消息啊

當(dāng)上一步?jīng)]有解決這個(gè)問題的時(shí)候,這一步,系統(tǒng)將會通過尋找有沒有別的對象可以幫忙處理這條消息.

系統(tǒng)會調(diào)用如下方法:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [RunPerson new];
}

大家可以看到,我在這個(gè)方法中返回了一個(gè)新的類RunPerson的實(shí)例,而在這個(gè)類的.m文件中,我寫了如下實(shí)現(xiàn):

-(void)run
{
    NSLog(@"我是會跑的人,我能跑!");
}

通過返回一個(gè)新的類,我們把這個(gè)消息轉(zhuǎn)發(fā)給了一個(gè)其它對象,讓這個(gè)對象來代替我們解決這個(gè)消息.

步驟三: 完整的消息轉(zhuǎn)發(fā)

在這一步中,消息轉(zhuǎn)發(fā)機(jī)制將進(jìn)行最后一步操作

我在Person的.m文件中寫了如下代碼,將會使得run消息,在消息轉(zhuǎn)發(fā)機(jī)制最后一步得到處理

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector==@selector(run)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector: aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector =[anInvocation selector];
    
    RunPerson *RP1=[RunPerson new];
    RunPerson *RP2=[RunPerson new];
    
    if ([RP1 respondsToSelector:selector]) {
        
        [anInvocation invokeWithTarget:RP1];
    }
    if ([RP2 respondsToSelector:selector]) {
        
        [anInvocation invokeWithTarget:RP2];
    }    
}

第一個(gè)方法返回的是一個(gè)方法簽名,而代碼中的v@:表示這個(gè)函數(shù)的性質(zhì),v代表返回值為void,@代表self,:代表_cmd;

當(dāng)有了方法簽名,消息轉(zhuǎn)發(fā)機(jī)制將會調(diào)用-(void)forwardInvocation:(NSInvocation )anInvocation方法,這個(gè)方法的參數(shù)anInvocation*為這個(gè)消息的全部內(nèi)容,包括調(diào)用者,參數(shù)等等.

在這個(gè)方法里我們初始化了兩個(gè)RunPerson實(shí)例,并把這個(gè)Invocation轉(zhuǎn)發(fā)給了這兩個(gè)RunPerson實(shí)例,這樣,這兩個(gè)RunPerson對象就都會對這個(gè)消息進(jìn)行處理了
這也是與第二個(gè)步驟最大的區(qū)別,可以轉(zhuǎn)發(fā)給多個(gè)對象進(jìn)行處理.

到此,消息轉(zhuǎn)發(fā)的三個(gè)步驟就結(jié)束了,如果到第三步還沒有解決這個(gè)消息,那么程序就會崩潰了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叠殷,更是在濱河造成了極大的恐慌嘉汰,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)灶体,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掐暮,“玉大人蝎抽,你說我怎么就攤上這事÷房耍” “怎么了樟结?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長精算。 經(jīng)常有香客問我瓢宦,道長,這世上最難降的妖魔是什么灰羽? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任驮履,我火速辦了婚禮,結(jié)果婚禮上廉嚼,老公的妹妹穿的比我還像新娘玫镐。我一直安慰自己,他們只是感情好前鹅,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布摘悴。 她就那樣靜靜地躺著,像睡著了一般舰绘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葱椭,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天捂寿,我揣著相機(jī)與錄音,去河邊找鬼孵运。 笑死秦陋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的治笨。 我是一名探鬼主播驳概,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼赤嚼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顺又?” 一聲冷哼從身側(cè)響起更卒,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稚照,沒想到半個(gè)月后蹂空,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡果录,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年上枕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弱恒。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辨萍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出返弹,到底是詐尸還是另有隱情分瘦,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布琉苇,位于F島的核電站嘲玫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏并扇。R本人自食惡果不足惜去团,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望穷蛹。 院中可真熱鬧土陪,春花似錦、人聲如沸肴熏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛙吏。三九已至源哩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸦做,已是汗流浹背励烦。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泼诱,地道東北人坛掠。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屉栓。 傳聞我的和親對象是個(gè)殘疾皇子舷蒲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

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