首先我們看一下objc_msgSend它具體是如何發(fā)送消息:
- 首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class
- 優(yōu)先在class的cache查找message方法组贺,如果找不到骂删,再到
methodLists查找 - 如果沒有在class找到腕让,再到super_class查找
-
一旦找到message這個方法痊乾,再依據(jù)receiver 中的self 指針找到當前的對象,調(diào)用當前對象的具體實現(xiàn)的方法(IMP),然后傳遞參數(shù),調(diào)用實現(xiàn)方法。
圖1
當對象收到無法解讀的消息后尖啡,就會啟動“消息轉(zhuǎn)發(fā)”(message forwarding)機制铁瞒,程序員可經(jīng)由此過程告訴對象應(yīng)該如何處理未知消息歌豺。
消息轉(zhuǎn)發(fā)全流程圖:
方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
首先,系統(tǒng)會調(diào)用resolveInstanceMethod
(當然僧著,如果這個方法是一個類方法履因,就會調(diào)用resolveClassMethod
)讓你自己為這個方法增加實現(xiàn)。
demo1:
定義一個Person,創(chuàng)建了一個Person類的對象p盹愚,然后調(diào)用p的run方法栅迄,注意,這個run方法是沒有寫實現(xiàn)的皆怕。
Person *p = [Person alloc] init];
[p run];
進入Person類的.m文件毅舆,我實現(xiàn)了resolveInstanceMethod
這個方法為我的Person類動態(tài)增加了一個run方法的實現(xiàn)。
void run (id self, SEL _cmd) {
NSLog(@"跑");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(run)){
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector
第二套方法端逼,forwardingTargetForSelector
朗兵,這個方法返回你需要轉(zhuǎn)發(fā)消息的對象。
demo2:
為了便于演示消息轉(zhuǎn)發(fā)顶滩,我們新建了一個汽車類Car余掖,并且實現(xiàn)了Car的run方法。
現(xiàn)在我不去對方案一的resolveInstanceMethod做任何處理礁鲁,直接調(diào)用父類方法盐欺。可以看到仅醇,系統(tǒng)已經(jīng)來到了forwardingTargetForSelector
方法冗美,我們現(xiàn)在返回一個Car類的實例對象。
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[Car alloc] init];
}
繼續(xù)運行析二,程序就來到了Car類的run方法粉洼,這樣节预,我們就實現(xiàn)了消息轉(zhuǎn)發(fā)。
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;
- (void)forwardInvocation:(NSInvocation *)invocation;
methodSignatureForSelector
用來生成方法簽名属韧,這個簽名就是給forwardInvocation
中的參數(shù)NSInvocation
調(diào)用的安拟。
開頭我們要找的錯誤unrecognized selector sent to instance
原因,原來就是因為methodSignatureForSelector
這個方法中宵喂,由于沒有找到run對應(yīng)的實現(xiàn)方法糠赦,所以返回了一個空的方法簽名,最終導(dǎo)致程序報錯崩潰锅棕。
所以我們需要做的是自己新建方法簽名拙泽,再在forwardInvocation
中用你要轉(zhuǎn)發(fā)的那個對象調(diào)用這個對應(yīng)的簽名,這樣也實現(xiàn)了消息轉(zhuǎn)發(fā)裸燎。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSString *sel = NSStringFromSelector(selector);
if([sel isEqualString:@"run"]) {
return [NSMethodSignature signatureWithObjcTypes:"v@:"];
}
return [sunper methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL selector = [invocation selector];
//新建需要轉(zhuǎn)發(fā)消息的對象
Car *car = [[Car alloc] init];
if([car respondsToSelector:selector]){
[invocation invokeWithTarget:car];
}
}
關(guān)于生成簽名的類型"v@:"解釋一下顾瞻。每一個方法會默認隱藏兩個參數(shù),self顺少、_cmd朋其,self代表方法調(diào)用者,_cmd代表這個方法的SEL脆炎,簽名類型就是用來描述這個方法的返回值梅猿、參數(shù)的,v代表返回值為void秒裕,@表示self袱蚓,:表示_cmd。
注意一點几蜻,前一套方案實現(xiàn)后一套方法就不會執(zhí)行喇潘。如果這幾套方案你都沒有做處理,那么程序就會報錯crash梭稚。