最近在重溫Effective Objective-C 2.0,這篇文章屬于重溫的產(chǎn)物吧,本文會(huì)通過(guò)demo來(lái)講解OC中的消息轉(zhuǎn)發(fā)機(jī)制

Demo:點(diǎn)我查看,覺(jué)得有幫助的話不要吝惜你的star
話不多說(shuō),iOS開(kāi)發(fā)過(guò)程中我們經(jīng)常會(huì)碰到這樣的報(bào)錯(cuò):unrecognized selector sent to instance **,原因是我們調(diào)用了一個(gè)不存在的方法。用OC消息機(jī)制來(lái)說(shuō)就是:消息的接收者不過(guò)到對(duì)應(yīng)的selector,這樣就啟動(dòng)了消息轉(zhuǎn)發(fā)機(jī)制,我們可以通過(guò)代碼在消息轉(zhuǎn)發(fā)的過(guò)程中告訴對(duì)象應(yīng)該如何處理未知的消息,默認(rèn)實(shí)現(xiàn)是拋出下面的異常

下面我們通過(guò)實(shí)例來(lái)看一下在拋出異常之前也就是消息轉(zhuǎn)發(fā)過(guò)程中都經(jīng)過(guò)了哪些步驟:
第一步:對(duì)象在收到無(wú)法解讀的消息后,首先會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel, 詢問(wèn)是否有動(dòng)態(tài)添加方法來(lái)進(jìn)行處理,處理實(shí)例如下
//People.m
void speak(id self, SEL _cmd){
NSLog(@"Now I can speak.");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));
if (sel == @selector(speak)) {
class_addMethod([self class], sel, (IMP)speak, "V@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
當(dāng)People 收到了未知 speak選擇子的消息的時(shí)候,如果是實(shí)例方法會(huì)首選調(diào)用上文的resolveInstanceMethod:方法,方法內(nèi)通過(guò)判斷選擇子然后通過(guò)class_addMethod方法動(dòng)態(tài)添加了一個(gè)speak的實(shí)現(xiàn)方法來(lái)解決掉這條未知的消息,此時(shí)消息轉(zhuǎn)發(fā)過(guò)程提前結(jié)束。
但是當(dāng)People 收到fly 這條未知消息的時(shí)候,第一步返回的是No,也就是沒(méi)有動(dòng)態(tài)新增實(shí)現(xiàn)方法的時(shí)候就會(huì)調(diào)用第二步
第二步:既然第一步已經(jīng)問(wèn)過(guò)了,沒(méi)有新增方法,那就問(wèn)問(wèn)有沒(méi)有別人能夠幫忙處理一下啊,調(diào)用的是- (id)forwardingTargetForSelector:(SEL)aSelector這個(gè)方法
上文我們說(shuō)到People接收到了一條選擇子為fly的未知消息,我們可以看到控制臺(tái)已經(jīng)打印了resolveInstanceMethod: fly,代表第一步已經(jīng)問(wèn)過(guò)了,那么第二步問(wèn)一下是否有別的類能幫忙處理嗎?代碼如下:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector: %@", NSStringFromSelector(aSelector));
Bird *bird = [[Bird alloc] init];
if ([bird respondsToSelector: aSelector]) {
return bird;
}
return [super forwardingTargetForSelector: aSelector];
}
// Bird.m
- (void)fly {
NSLog(@"I am a bird, I can fly.");
}
通過(guò)- (id)forwardingTargetForSelector:(SEL)aSelector的處理,bird能夠處理這條消息,所以這條消息被bird成功處理,消息轉(zhuǎn)發(fā)流程提前結(jié)束??刂婆_(tái)打印
forwardingTargetForSelector: fly
I am a bird, I can fly.
但是如果- (id)forwardingTargetForSelector:(SEL)aSelector也找不到能夠幫忙處理這條未知消息,那就會(huì)走到最后一步,這步也是代價(jià)最大的一步
第三步:調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation,在調(diào)用forwardInvocation:之前會(huì)調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法來(lái)獲取這個(gè)選擇子的方法簽名,然后在-(void)forwardInvocation:(NSInvocation *)anInvocation方法中你就可以通過(guò)anInvocation拿到相應(yīng)信息做處理,實(shí)例代碼如下
當(dāng)People 收到一條 選擇子為code 的消息的時(shí)候,前兩步發(fā)現(xiàn)都沒(méi)辦法處理掉,走到第三步:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation: %@", NSStringFromSelector([anInvocation selector]));
if ([anInvocation selector] == @selector(code)) {
Monkey *monkey = [[Monkey alloc] init];
[anInvocation invokeWithTarget:monkey];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"method signature for selector: %@", NSStringFromSelector(aSelector));
if (aSelector == @selector(code)) {
return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
這時(shí)控制臺(tái)會(huì)打印
resolveInstanceMethod: code
forwardingTargetForSelector: code
method signature for selector: code
forwardInvocation: code
I am a coder.
此時(shí)這個(gè)code消息已經(jīng)被monkey實(shí)例處理掉
此時(shí)消息轉(zhuǎn)發(fā)流程完整的結(jié)束了,完整的消息轉(zhuǎn)發(fā)流程如下:

那么最后消息未能處理的時(shí)候,還會(huì)調(diào)用到
- (void)doesNotRecognizeSelector:(SEL)aSelector這個(gè)方法,我們也可以在這個(gè)方法中做些文章,避免掉crash,但是只建議在線上環(huán)境的時(shí)候做處理,實(shí)際開(kāi)發(fā)過(guò)程中還要把異常拋出來(lái)
EOF:OC中消息轉(zhuǎn)發(fā)流程大概就是這樣了,Demo點(diǎn)這里,覺(jué)得有幫助的話不要吝惜你的star,由于個(gè)人能力有限,文中難免有些錯(cuò)誤,希望大家不吝賜教~
另外有一個(gè)問(wèn)題想問(wèn)大家,+ (BOOL)resolveClassMethod:(SEL)sel 在這個(gè)方法中怎么動(dòng)態(tài)添加類方法? 比如我發(fā)送了一條未知的 [People missMethod]消息,怎么添加 +(void)missMethod 的實(shí)現(xiàn)呢?
//2018-03-09補(bǔ)充
早早已經(jīng)找到答案,忘記更新文章了??,class_addMethod的第一參數(shù)換成[self superclass]即可。