iOS理解Objective-C中消息轉(zhuǎn)發(fā)機(jī)制附Demo

最近在重溫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)是拋出下面的異常

unrecognized selector.png

下面我們通過(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]即可。

擴(kuò)展閱讀

????Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容