iOS - 動態(tài)添加方法和消息轉(zhuǎn)發(fā)

假設(shè)

假設(shè),聲明一個 TestObect 的類,.h中聲明了 test方法,但是.m中未實(shí)現(xiàn),但是調(diào)用了這個方法會報一個經(jīng)典的錯誤

沒有找到方法實(shí)現(xiàn)

ok,前提已經(jīng)說完了,我們就從找這個錯誤原因講起。

解決

首先,該方法在調(diào)用時,系統(tǒng)會查看這個對象能否接收這個消息(查看這個類有沒有這個方法,或者有沒有實(shí)現(xiàn)這個方法。),如果不能并且只在不能的情況下,就會調(diào)用下面這幾個方法,給你“補(bǔ)救”的機(jī)會,你可以先理解為幾套防止程序crash的備選方案,我們就是利用這幾個方案進(jìn)行消息轉(zhuǎn)發(fā),注意一點(diǎn),前一套方案實(shí)現(xiàn)后一套方法就不會執(zhí)行。如果這幾套方案你都沒有做處理,那么程序就會報錯crash。

方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector;
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

抽象解釋一下消息轉(zhuǎn)發(fā): 比賽足球時,腳下有球的那名球員,如果他的位置不利于射門或者他的球即將被對方球員搶斷,這時最好是把球傳出去,這里的球就相當(dāng)于消息。

1.resolveInstanceMethod

當(dāng)你調(diào)用方法時,系統(tǒng)會調(diào)用 resolveInstanceMethod 這個方法,參數(shù)SEL就是你調(diào)用的方法,那么我們可以重寫這個方法,從而實(shí)現(xiàn)動態(tài)添加一個方法,來使程序不會崩潰.

// 需要導(dǎo)入 #import <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {        
        IMP imp = class_getMethodImplementation(self, @selector(run));
        class_addMethod([self class], sel, imp, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
動態(tài)添加方法
科普一下
  1. IMP獲取
    如果添加方法實(shí)現(xiàn)是用OC風(fēng)格寫的 那么用 class_getMethodImplementation 來獲取IMP
    如果是用C風(fēng)格寫的函數(shù)
void run(id self, SEL _cmd) {
    NSLog(@" %@ %s", sel_getName(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        class_addMethod([self class], sel, (IMP)run, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
  1. class_addMethod 參數(shù)
  • 參數(shù)一: Class:我們需要一個class,比如我的[self class]。

  • 參數(shù)二: SEL:方法

  • 參數(shù)三: IMP :IMP就是Implementation的縮寫,它是指向一個方法實(shí)現(xiàn)的指針,每一個方法都有一個對應(yīng)的IMP。這里需要的是IMP,所以你不能直接寫方法,參照上述 1 來獲取

  • 參數(shù)四: const char *types:
    ”v@:”意思就是這已是一個void類型的方法,沒有參數(shù)傳入。
    “i@:”就是說這是一個int類型的方法,沒有參數(shù)傳入。
    ”i@:@”就是說這是一個int類型的方法,有一個對象參數(shù)傳入。
    因?yàn)槊恳粋€方法會默認(rèn)隱藏兩個參數(shù),self、_cmd,self代表方法調(diào)用者,_cmd代表這個方法的SEL,該參數(shù)就是用來描述這個方法的返回值、參數(shù)的,v 代表返回值為void,@ 表示self,: 表示_cmd。
    詳見官網(wǎng)

  • 注意: 用這個方法添加的方法是無法直接調(diào)用的,必須用performSelector:調(diào)用。
    因?yàn)閜erformSelector是運(yùn)行時系統(tǒng)負(fù)責(zé)去找方法的,在編譯時候不做任何校驗(yàn);如果直接調(diào)用編譯是會自動校驗(yàn)。 然后class_addMethod添加方法是在運(yùn)行時添加的,你在編譯的時候還沒有這個本類方法,所以當(dāng)然不行啦。

2.forwardingTargetForSelector

forwardingTargetForSelector 是NSObject的函數(shù),來決定誰執(zhí)行方法

TestObect.m
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [ForwardTestObect new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

ForwardTestObect.m
- (void)test{
    NSLog(@"%@  --- test ---",self);
}
打印截圖
3.methodSignatureForSelector和forwardInvocation

methodSignatureForSelector用來生成方法簽名,這個簽名就是給forwardInvocation中的參數(shù)NSInvocation調(diào)用的。
開頭我們要找的錯誤unrecognized selector sent to instance原因,原來就是因?yàn)閙ethodSignatureForSelector這個方法中,由于沒有找到run對應(yīng)的實(shí)現(xiàn)方法,所以返回了一個空的方法簽名,最終導(dǎo)致程序報錯崩潰。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL sel = [anInvocation selector];
    //創(chuàng)建接受對象
    ForwardTestObect *forward = [ForwardTestObect new];
    //判斷是否實(shí)現(xiàn)了這個方法
    if ([forward respondsToSelector:sel]) {
        // 喚醒這個方法
        [anInvocation invokeWithTarget:forward];
    }else{
        [super forwardInvocation:anInvocation];
    }
}
打印截圖

其中: [NSMethodSignature signatureWithObjCTypes:"v@:"]; 方法的參數(shù)參考上述 class_addMethod 方法的參數(shù)四
forwardInvocation: 方法就是一個不能識別消息的分發(fā)中心,將這些不能識別的消息轉(zhuǎn)發(fā)給不同的接收對象,或者轉(zhuǎn)發(fā)給同一個對象,再或者將消息翻譯成另外的消息,亦或者簡單的“吃掉”某些消息,因此沒有響應(yīng)也不會報錯。這一切都取決于方法的具體實(shí)現(xiàn)。
注意:forwardInvocation:方法只有在消息接收對象中無法正常響應(yīng)消息時才會被調(diào)用。所以,如果我們向往一個對象將一個消息轉(zhuǎn)發(fā)給其他對象時,要確保這個對象不能有該消息的所對應(yīng)的方法。否則,forwardInvocation:將不可能被調(diào)用。

最后

1、調(diào)用resolveInstanceMethod給個機(jī)會讓類添加這個實(shí)現(xiàn)這個函數(shù)
2、調(diào)用forwardingTargetForSelector讓別的對象去執(zhí)行這個函數(shù)
3、調(diào)用methodSignatureForSelector(函數(shù)符號制造器)和forwardInvocation(函數(shù)執(zhí)行器)靈活的將目標(biāo)函數(shù)以其他形式執(zhí)行。
如果都不中,調(diào)用doesNotRecognizeSelector拋出異常。

方案1是動態(tài)添加方法,方案2和方案3 就是OC的消息轉(zhuǎn)發(fā)機(jī)制

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,072評論 0 9
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,990評論 2 3
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 888評論 0 1
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,249評論 0 9
  • 滄龍教授 ——從城坊間、鄉(xiāng)土系列人士之“盛世滄龍” 火山 早在友維的明輝畫廊就聽說有“盛世滄龍”這么一個人,看到他...
    朱明云閱讀 779評論 2 3

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