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

當(dāng)我們像一個對象發(fā)送消息[Receiver message]衔彻,Receiver沒有實(shí)現(xiàn)該消息,即[Receiver respondsToSelector:SEL]返回為NO情況下戚揭,其實(shí)系統(tǒng)不會立刻出現(xiàn)cash,這時Runtime system會對message進(jìn)行轉(zhuǎn)發(fā)缔俄。轉(zhuǎn)發(fā)之后烙懦,如果該消息依然沒有被執(zhí)行就會出現(xiàn)Cash!Runtime System為我們提供了三種解決這種給對象發(fā)送沒有實(shí)現(xiàn)消息方案昆烁。

消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個步驟:

1. 動態(tài)方法解析

2. 備用接收者

3. 完整轉(zhuǎn)發(fā)

我們可以通過控制這三個步驟其中一環(huán)來解決這一個問題

特別注意:如果是正常類的消息吊骤,是不會走到這三個步驟的。所以走到這三個不步驟的前提條件已經(jīng)確定該消息為未知消息


這篇博客的前置知識點(diǎn)是 OC 的消息傳遞機(jī)制静尼,如果你對此還不了解白粉,請先學(xué)習(xí)之,再來看這篇鼠渺。這篇博客我嘗試用口語的方式像講述 PPT 一樣給大家講述這個知識點(diǎn)鸭巴。

我們來思考一個問題,如果對象在收到無法解讀的消息時拦盹,會發(fā)生什么鹃祖?例如,我們實(shí)現(xiàn)一個 viewcontroller掌敬,其中并沒有一個成員方法名為『setText:』,當(dāng)編寫這條語句時

[selfsetText:@"你好"];

示例

由于 OC 是一門動態(tài)語言惯豆,在編譯期只是顯示一條 warning池磁,而不是阻止運(yùn)行的 error奔害。如果忽略 warning 運(yùn)行楷兽,程序會 crash,在控制臺會顯示類似

unrecognized selector sent to instance0x7f931a4180d0

的報(bào)錯信息华临。

unrecognized selector

消息被發(fā)送給了不能處理它的對象芯杀。我們學(xué)習(xí) iOS 的消息轉(zhuǎn)發(fā)機(jī)制可不是為了故意造這樣的 crash 玩,說上面的這個例子雅潭,是為了說明如果我們不通過消息轉(zhuǎn)發(fā)機(jī)制做任何事情的話揭厚,系統(tǒng)最終會以 crash 結(jié)束。等等扶供,剛才我們說到 OC 是一門動態(tài)語言筛圆,那么是否可以在運(yùn)行期做一些事來讓 crash 不會發(fā)生呢?

消息轉(zhuǎn)發(fā)機(jī)制就是來干這件事的椿浓,在運(yùn)行期通過3個『接盤俠』方法太援,給對象和消息更多的機(jī)會來完成成功的調(diào)用,而不是直接 crash扳碍。

一號接盤俠

第一個接盤俠代表動態(tài)方法解析階段提岔,對應(yīng)的具體方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,當(dāng)方法是實(shí)例方法時調(diào)用前者笋敞,當(dāng)方法為類方法時碱蒙,調(diào)用后者。這個方法設(shè)計(jì)的目的是為了給類利用 class_addMethod 添加方法的機(jī)會夯巷。

看下面這個示例赛惩,MyTestObject類重寫了第一個接盤俠方法,可以看到這個方法傳入一個 selector趁餐,返回 BOOL 類型坊秸。被傳入的 selector 就是未被處理的方法,在一號接盤俠方法中澎怒,判斷若方法名為 XXX 則給這個類添加同名的方法褒搔,把方法的實(shí)現(xiàn)指向跟 XXX 名字不一致的 AAA,并返回 YES喷面。若 selector 名字不是 XXX星瘾,就返回父類。

resolveInstanceMethod

通過這個示例惧辈,可以看出琳状,我們可以通過一號接盤俠方法讓 方法名和方法實(shí)現(xiàn)在運(yùn)行期任意搭配。

再說一下這個返回值盒齿,其實(shí)可以試驗(yàn)一下念逞,無論返回 YES 還是 NO困食,系統(tǒng)都會嘗試用 SEL 來尋找 IMP,如果找到函數(shù)實(shí)現(xiàn)翎承,則執(zhí)行硕盹,所以無論返回 YES\NO都會進(jìn)入二號接盤俠方法。

二號接盤俠

第二個階段是備援接收者階段叨咖,對象的具體方法是-(id)forwardingTargetForSelector:(SEL)aSelector 瘩例,此時,運(yùn)行時詢問能否把消息轉(zhuǎn)給其他接收者處理甸各,也就是此時系統(tǒng)給了個將這個 SEL 轉(zhuǎn)給其他對象的機(jī)會垛贤。我們繼續(xù)來研究下參數(shù)和返回值,參數(shù)和一號接盤俠一樣趣倾,都是 selector聘惦,返回值是 id 類型,當(dāng)返回 非self\非nil 時儒恋,消息被轉(zhuǎn)給新對象執(zhí)行善绎。

forwardingTargetForSelector

三號接盤俠

第三個階段是完整消息轉(zhuǎn)發(fā)階段,對應(yīng)方法-(void)forwardInvocation:(NSInvocation *)anInvocation碧浊,這是消息轉(zhuǎn)發(fā)流程的最后一個環(huán)節(jié)涂邀。參數(shù) anInvocation 中包含未處理消息的各種信息(selector\target\參數(shù)...)。在這個方法中箱锐,可以把 anInvocation 轉(zhuǎn)發(fā)給多個對象比勉,與二號接盤俠不同,二號只能轉(zhuǎn)給一個對象驹止。

forwardInvocation

如果上述3個方法都沒有來處理這個消息浩聋,就會進(jìn)入 NSObject 的-(void)doesNotRecognizeSelector:(SEL)aSelector方法中,拋出異常臊恋。等等衣洁,為什么我們不能通過給 NSObject 創(chuàng)建一個 category,重寫這個方法抖仅,在這里處理消息未被處理的情況呀坊夫?在蘋果的官方文檔中,明確提到撤卢,“一定不能讓這個函數(shù)就這么結(jié)束掉环凿,必須拋出異常”放吩。除了聽官方文檔的話智听,其實(shí)在分類中通過重寫該方法處理各種消息未被處理的情況,會讓這個分類的方法特別長,不利于維護(hù)到推。而且還有個原因考赛,明明方法名叫『無法識別 selector』,其中卻是一大堆處理該情況的代碼莉测,也很奇怪颜骤。

doesNotRecognizeSelector

總結(jié)

總結(jié)一下整個消息轉(zhuǎn)發(fā)的流程:

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

可以通過重寫3個接盤俠方法,在其中打斷點(diǎn)來驗(yàn)證執(zhí)行順序悔雹。

斷點(diǎn)驗(yàn)證順序

總結(jié):

在一個函數(shù)找不到時复哆,OC提供了三種方式去補(bǔ)救:

1欣喧、調(diào)用resolveInstanceMethod給個機(jī)會讓類添加這個實(shí)現(xiàn)這個函數(shù)

2腌零、調(diào)用forwardingTargetForSelector讓別的對象去執(zhí)行這個函數(shù)

3、調(diào)用forwardInvocation(函數(shù)執(zhí)行器)靈活的將目標(biāo)函數(shù)以其他形式執(zhí)行唆阿。

如果都不中益涧,調(diào)用doesNotRecognizeSelector拋出異常。

疑問

Q1:那我們只用最后一個接盤俠方法多好啊驯鳖,為什么還需要前2個呢闲询?

其實(shí)還與這3個方法的用途不同有關(guān):

運(yùn)行期添加方法,用1浅辙;

轉(zhuǎn)發(fā)給另1個對象扭弧、改變方法時,用2记舆;

需要轉(zhuǎn)發(fā)給多個對象時鸽捻,用3;

而且泽腮,步驟越往后御蒲,處理消息的代價越大,到最后一個階段時诊赊,都創(chuàng)建了 NSInvocation 對象了厚满。

Q2:消息轉(zhuǎn)發(fā)有哪些應(yīng)用場景呢?

可以在運(yùn)行期再加入某方法,例如 Teacher 類里有teach方法碧磅,DrugDealer 類里有l(wèi)etsCook方法碘箍,通過一號接盤俠方法,我們可以在運(yùn)行期把 saleDrug 偷摸加到 teacher 的方法列表中鲸郊,讓 teacher 具備販毒的功能丰榴,[teacher? guessWhatHeDo],實(shí)際調(diào)用的是[teacher letsCook]严望,唉呀媽呀多艇,絕命毒師啊。

把方法轉(zhuǎn)給其他對象處理像吻,再舉個例子峻黍,還是 Teacher 類(博主跟老師有仇嗎...)复隆,[teacher letsCook],可以把對象在運(yùn)行期換為drugDealer姆涩。再來一個 Cook 類挽拂,也有 letsCook 方法,但這次這方法不是 cook 毒品骨饿,而是 cook 菜亏栈。因此既可以通過[teacher letsCook] 實(shí)現(xiàn)[drugDealer letsCook],也可以實(shí)現(xiàn)[cook letsCook]。相當(dāng)于 OC 實(shí)現(xiàn)了多重繼承宏赘,雖然有點(diǎn)不太恰當(dāng)...

注意

respondsToSelector我們再熟悉不過了绒北,用來檢查某對象是否實(shí)現(xiàn)了某方法。此函數(shù)通常是不需要重載的察署,但是在動態(tài)實(shí)現(xiàn)了查找過程后闷游,需要重載此函數(shù)讓對外接口查找動態(tài)實(shí)現(xiàn)函數(shù)的時候返回YES,保證對外接口的行為統(tǒng)一贴汪。

respondsToSelector

最后說一下 warning 的事脐往。編譯器很好心的報(bào)的那個 warning 咋辦呢,不管那個小黃條不是一個愛整潔的程序員的風(fēng)格扳埂,所以我們要想辦法把它去掉业簿。

有兩種方法,第一種比較暴力阳懂,通過在配置文件中把 Complier Flag 加-w梅尤,對該類去除所有 warning。

去掉所有warning

第二種是推薦的做法希太,在 xcode 的 error 面板對 warning 右鍵-Reveal in Log,這里有個小 bug克饶,如果這個選項(xiàng)不可選擇,需要你重新 build 一下就可選了誊辉,

小 Bug

在右側(cè)矾湃,可以看到這個warning 的名稱,

如何看warning名稱

所以用這個宏把出現(xiàn) warning 的代碼包圍起來堕澄,就可以讓編譯器不再報(bào)錯:

#pragmaclang diagnostic push#pragmaclang diagnostic ignored"-Wobjc-method-access"[self setText:@"你好"];#pragmaclang diagnostic pop

文/毀小慕(簡書作者)

原文鏈接:http://www.reibang.com/p/fa29c920409d

著作權(quán)歸作者所有邀跃,轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),并標(biāo)注“簡書作者”蛙紫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拍屑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坑傅,更是在濱河造成了極大的恐慌僵驰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒜茴,居然都是意外死亡星爪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門粉私,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顽腾,“玉大人,你說我怎么就攤上這事诺核〕ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵窖杀,是天一觀的道長漓摩。 經(jīng)常有香客問我,道長陈瘦,這世上最難降的妖魔是什么幌甘? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任潮售,我火速辦了婚禮痊项,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酥诽。我一直安慰自己鞍泉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布肮帐。 她就那樣靜靜地躺著咖驮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪训枢。 梳的紋絲不亂的頭發(fā)上托修,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音恒界,去河邊找鬼睦刃。 笑死,一個胖子當(dāng)著我的面吹牛十酣,可吹牛的內(nèi)容都是我干的涩拙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耸采,長吁一口氣:“原來是場噩夢啊……” “哼兴泥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虾宇,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤搓彻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旭贬,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竭沫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骑篙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜕提。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖靶端,靈堂內(nèi)的尸體忽然破棺而出谎势,到底是詐尸還是另有隱情,我是刑警寧澤杨名,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布脏榆,位于F島的核電站,受9級特大地震影響台谍,放射性物質(zhì)發(fā)生泄漏须喂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一趁蕊、第九天 我趴在偏房一處隱蔽的房頂上張望坞生。 院中可真熱鬧,春花似錦掷伙、人聲如沸是己。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卒废。三九已至,卻和暖如春宙地,著一層夾襖步出監(jiān)牢的瞬間摔认,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工宅粥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留参袱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓粹胯,卻偏偏與公主長得像蓖柔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子风纠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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