正常情況下皮假,要想讓對(duì)象能理解某條消息鞋拟,那么我們必須以程序碼實(shí)現(xiàn)出對(duì)應(yīng)的方法才行。但是惹资,在編譯期間贺纲,向?qū)ο蟀l(fā)送了一個(gè)未實(shí)現(xiàn)的消息,這并不會(huì)報(bào)錯(cuò)褪测,因?yàn)樵谶\(yùn)行時(shí)可以繼續(xù)添加方法猴誊。
如果向?qū)ο髠鬟f了一個(gè)未知的消息潦刃,而對(duì)象沒(méi)法在自己所屬的類中、父類......乃至根類中稠肘,找到對(duì)應(yīng)的消息實(shí)現(xiàn)方法福铅,就會(huì)報(bào)錯(cuò)閃退萝毛。身為程序員项阴,crash是決定不能接受的啊。遇到這種問(wèn)題笆包,除了坐等crash环揽,難道就沒(méi)有別的法子了?這就是我們今天要了解的內(nèi)容庵佣。
其實(shí)歉胶,在對(duì)象沒(méi)法處理未知消息、程序crash之前巴粪,我們還有三個(gè)補(bǔ)救的機(jī)會(huì)通今,這就是傳說(shuō)中的消息轉(zhuǎn)發(fā)機(jī)制。
消息轉(zhuǎn)發(fā)肛根,分四個(gè)步驟辫塌。
步驟一:動(dòng)態(tài)方法解析。+(BOOL)resolveInstanceMethod:(SEL)name;
先征詢對(duì)象所屬的類派哲,看其是否能動(dòng)態(tài)添加方法臼氨,以處理當(dāng)前這個(gè)未知的消息。如果能芭届,那么消息轉(zhuǎn)發(fā)結(jié)束储矩。如果不能,進(jìn)入步驟二褂乍。
步驟二:備援接收者持隧。- (id)forwardingTargetForSelector:(SEL)aSelector;
再次征詢對(duì)象所屬的類,看其是否能找到別的對(duì)象來(lái)處理這個(gè)消息逃片,說(shuō)白了舆蝴,就是打算找個(gè)有能力的人,把這個(gè)燙手山芋扔給那個(gè)人來(lái)處理题诵。同樣的道理洁仗,如果能找到接盤(pán)俠,那么消息轉(zhuǎn)發(fā)結(jié)束性锭。如果不能赠潦,那么進(jìn)入步驟三。
步驟三草冈,消息重定向她奥。- (void)forwardInvocation: (NSInvocation*)invocation;
又稱為完整的消息轉(zhuǎn)發(fā)機(jī)制瓮增。到了這里,runtime 就會(huì)把與消息有關(guān)的全部細(xì)節(jié)哩俭,都分裝到NSInvocation 對(duì)象中绷跑,再給消息接受者最后一個(gè)機(jī)會(huì),讓其設(shè)法解決當(dāng)前還未處理的這條消息凡资。即對(duì)象調(diào)用 forwardInvocation: 方法砸捏,如果不能處理就會(huì)調(diào)用父類的相關(guān)方法,一直到NSObject的這個(gè)方法隙赁,如果NSObject都無(wú)法處理就會(huì)調(diào)用doesNotRecognizeSelector: 方法拋出異常垦藏,程序crash。
有個(gè)流程圖伞访,解釋的很清楚:
最后掂骏,關(guān)于步驟二,其實(shí)可以用來(lái)模擬“多重繼承”厚掷。比如說(shuō)弟灼,有一個(gè)a對(duì)象,它的內(nèi)部還有其他一系列其他的對(duì)象冒黑。那么a對(duì)象可以利用步驟二田绑,讓其他內(nèi)部對(duì)象去處理某個(gè)消息,而在外部看來(lái)薛闪,以為是a對(duì)象本身去處理的辛馆,感覺(jué)跟類簇差不多?
但有個(gè)點(diǎn)需要注意豁延,步驟二中昙篙,我們是沒(méi)法去修改這個(gè)消息的內(nèi)容的。
最后的最后诱咏,步驟三苔可,可以修改消息的內(nèi)容。運(yùn)行時(shí)在調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation之前袋狞,會(huì)先調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法來(lái)獲取這個(gè)選擇子的方法簽名焚辅,然后在 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法中我們就可以通過(guò)anInvocation拿到相應(yīng)信息做處理。具體的就不展開(kāi)了苟鸯,自行谷歌同蜻。