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

之前寫了篇關(guān)于消息發(fā)送的文章,其中提及到了消息轉(zhuǎn)發(fā)运褪,由于 OC 是一門動態(tài)的語言惊楼,在編譯時,編譯器還無法確定到底有沒有這個方法秸讹,因為在運行時我們還可以動態(tài)的給它添加對應(yīng)的方法處理檀咙,我曾說過,當(dāng)一個方法在自己或其繼承結(jié)構(gòu)上面的方法列表中都沒有找到璃诀,就會啟動對應(yīng)的 "消息轉(zhuǎn)發(fā)" 機制弧可。

好,現(xiàn)在我們開始好好說說 "消息轉(zhuǎn)發(fā)" 究竟是怎么一回事劣欢∽厮校總得來說,消息轉(zhuǎn)發(fā)的流程分為兩大階段氧秘。先來說第一階段年鸳,在第一階段時,當(dāng)接受者無法處理發(fā)送的消息時丸相,首先會觸發(fā)當(dāng)前類的 resolveInstanceMethod: 或者對應(yīng)的 resolveClassMethod: 方法搔确,在這個方法中會詢問接受者,是否能夠動態(tài)的給它添加對應(yīng)的方法來處理這個未知的消息灭忠,這個行為的專業(yè)術(shù)語叫 "動態(tài)方法解析",在這個方法中膳算,系統(tǒng)會將未處理的那個 @selector(方法) 名傳遞給我們,然后讓我們返回一個 BOOL 值弛作,詢問是否能夠添加一個實例或者類方法來處理它涕蜂,當(dāng)我們直接返回一個 NO ,或者直接調(diào)用父類方法讓父類處理它時,自然就會崩掉映琳,舉個例子:

Person *p = [[Person alloc]init];
 objc_msgSend(p,@selector(eat));

在上面的代碼中机隙,Person 這個類里是沒有 eat 這個實例方法的,再看下面:

@implementation Person
 + (BOOL)resolveInstanceMethod:(SEL)sel{
return [super resolveInstanceMethod:sel];
}
@end 

在上面的代碼中萨西,我直接調(diào)用了父類的處理方法有鹿,當(dāng)然,這其實和不寫是沒有區(qū)別的谎脯,我們現(xiàn)在看看運行后的報錯信息

reason: '-[Person eat]: unrecognized selector sent to instance 0x7ff64350fdf0'

不管英語是好是差葱跋,作為一個程序員,看到這種報錯信息,哪怕你不懂英語也能看出來錯誤原因了吧 "向?qū)嵗l(fā)送了一個未識別的消息" ,那個 0x7ff64350fdf0 也就是當(dāng)前 p 對象的地址娱俺,其實報錯信息里還有一條重要的信息:

0   CoreFoundation                      0x0000000105896d85 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x000000010530adeb objc_exception_throw + 48
2   CoreFoundation                      0x000000010589fd3d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205

其中的 [NSObject(NSObject) doesNotRecognizeSelector:] + 205 這個方法應(yīng)該說是消息傳遞過程中的最后一步稍味,當(dāng)沒有任何措施處理這個未識別的消息,便會調(diào)用 NSObject的doesNotRecognizeSelector:方法默認實現(xiàn)來拋出異常荠卷。
那么模庐,如何在第一階段來攔截這個消息轉(zhuǎn)發(fā)的過程,進行 "動態(tài)方法解析呢" ? OK僵朗,現(xiàn)在我們來試一下:

 @implementation Person
void change(){
NSLog(@"我是被添加的方法");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{

class_addMethod(self, sel, (IMP)change, "v@:");

return YES;
}
@end

在上面的代碼中,我給它創(chuàng)建了一個 change 函數(shù)赖欣,在 resolveInstanceMethod 方法中給它添加進了 class_addMethod 這個函數(shù),這個函數(shù)中验庙,第一個參數(shù)是需要添加方法的類顶吮,第二個參數(shù)是需要添加的方法名,第三個參數(shù)是指向函數(shù)的IMP指針悴了,第四個參數(shù)是待添加方法的類型編碼(類型編碼:編碼開頭的字符表示方法的返回值類型,后面的字符表示各個參數(shù)),并且將 resolveInstanceMethod: 方法返回了 YES ,表示能夠動態(tài)的添加方法來處理這個未識別的消息违寿,這樣一來湃交,控制臺便會打印 change 函數(shù)中打印的那段字符串。

好了藤巢,剛剛前面部分其實說的都是第一階段搞莺,那部分報錯信息應(yīng)該說是第二階段的末尾部分,大家先別關(guān)注掂咒,接著往下看才沧。
現(xiàn)在說第二階段,如果在第一階段時绍刮,接收者沒有進行 "動態(tài)方法解析" ,那么就會進入第二階段温圆,這時接收者已經(jīng)無法再利用動態(tài)添加方法來處理未識別的消息了,但是孩革,這時候接收者也可以用其他手段來處理這條消息岁歉,這時的第二階段又得分為兩步,第一步膝蜈,運行時系統(tǒng)會詢問接收者是否有其他對象來幫助其處理這條消息锅移, 具體會調(diào)用當(dāng)前類的如下方法進行詢問:
- (id)forwardingTargetForSelector:(SEL)aSelector{
}

從上面的代碼中可以看到,該方法會返回一個 id 類型的對象饱搏,意思就是帆啃,如果有其他對象可以幫助它處理這條消息,就將那個對象返回窍帝,如果沒有,就返回 nil诽偷,但是坤学,請注意疯坤,剛剛也說了,在這第二階段深浮,已經(jīng)沒有辦法在這里給它動態(tài)添加方法了压怠,所以千萬不要把其寫在這個方法中。

經(jīng)常剛剛的第二階段的第一步操作后飞苇,如果已經(jīng)返回了可以處理該消息的對象菌瘫,那么該消息就被處理,過程便結(jié)束布卡。但是如果返回的是 nil雨让, 那么就會進入到第二步,也就是啟動 "完整的消息轉(zhuǎn)發(fā)機制" 了,這時忿等,首先會創(chuàng)建一個 NSInvocation 類型的對象栖忠,可以點到頭文件去看,這個對象里包含了 selector 贸街、target(調(diào)用目標) 以及參數(shù)庵寞,當(dāng)創(chuàng)建了這個對象,運行時會把消息傳遞給這個對象中薛匪,并且調(diào)用下面的方法來轉(zhuǎn)發(fā)消息:
- (void)forwardInvocation:(NSInvocation *)anInvocation{

}

在上面的代碼中捐川,你只需要在實現(xiàn)里改變 anInvocation 對象的 target (調(diào)用目標) 屬性,讓消息的在新的目標上得到實現(xiàn)即可逸尖,其實這個跟上面的第一步是差不多的古沥,也很少有人用到這些,如果依然沒有新的目標來處理這個消息冷溶,這個類就會按照繼承體系渐白,一直向上尋找有沒有父類及以上處理了forwardInvocation:這個方法,直至 NSObject 類逞频,如果一直到NSObject 類之前都沒有哪個類來處理纯衍,那么調(diào)用完 NSObject 類這個方法后,繼而調(diào)用 NSObject 類的 doesNotRecognizeSelector 方法苗胀,拋出異常襟诸,表明這個消息最終沒有被處理。
消息轉(zhuǎn)發(fā)部分我要說的基本就這么多基协,下面這張圖很好的表達了消息轉(zhuǎn)發(fā)的整個過程:

Snip20160830_3.png

其實歌亲,我們在開發(fā)中基本上不需要管第二階段的那些流程,最好的方式都是在第一階段中的動態(tài)添加方法中給它處理好澜驮,當(dāng)然陷揪,筆者說的這些都是自己的看法,有不對的地方也希望各位能夠指出,??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悍缠,一起剝皮案震驚了整個濱河市卦绣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌飞蚓,老刑警劉巖滤港,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異趴拧,居然都是意外死亡溅漾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門著榴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來添履,“玉大人,你說我怎么就攤上這事兄渺》炝洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵挂谍,是天一觀的道長叔壤。 經(jīng)常有香客問我,道長口叙,這世上最難降的妖魔是什么炼绘? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮妄田,結(jié)果婚禮上俺亮,老公的妹妹穿的比我還像新娘。我一直安慰自己疟呐,他們只是感情好脚曾,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著启具,像睡著了一般本讥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲁冯,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天拷沸,我揣著相機與錄音,去河邊找鬼薯演。 笑死撞芍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跨扮。 我是一名探鬼主播序无,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼验毡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愉镰?” 一聲冷哼從身側(cè)響起米罚,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丈探,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拔莱,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡碗降,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了塘秦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讼渊。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尊剔,靈堂內(nèi)的尸體忽然破棺而出爪幻,到底是詐尸還是另有隱情,我是刑警寧澤须误,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布挨稿,位于F島的核電站,受9級特大地震影響京痢,放射性物質(zhì)發(fā)生泄漏奶甘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一祭椰、第九天 我趴在偏房一處隱蔽的房頂上張望臭家。 院中可真熱鬧,春花似錦方淤、人聲如沸钉赁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽你踩。三九已至,卻和暖如春邑蒋,著一層夾襖步出監(jiān)牢的瞬間姓蜂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工医吊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脊另,地道東北人氮趋。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嚣伐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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