前言
在上一篇Runtime源碼 方法調(diào)用的過程中我們了解了消息的響應(yīng)過程,即
- 先緩存查找,若未找到
- 接下來查找本類的方法列表查找,若未找到
- 則遞歸繼承體系查找父類的方法列表直到NSObject
在第二第三步過程中,如果找到了則響應(yīng)消息,并填充緩存,緩存是保存在元類上的,如果還找不到,則進(jìn)入接下來的消息轉(zhuǎn)發(fā)流程。
消息轉(zhuǎn)發(fā)流程也分為三個(gè)步驟,動(dòng)態(tài)解析 -> 前端轉(zhuǎn)發(fā) -> 方法簽名轉(zhuǎn)發(fā),流程如下:

我們結(jié)合一個(gè)實(shí)例來具體看看傳送門:QFMessageForwardDemo
動(dòng)態(tài)解析
第一步,當(dāng)沒找到方法時(shí),你可以通過+ (BOOL)resolveInstanceMethod:(SEL)sel和+ (BOOL)resolveClassMethod:(SEL)sel來添加實(shí)例方法和類方法
這里我們?cè)?code>ViewController中直接調(diào)用了QFPerson的run方法,但是QFPerson并沒有實(shí)現(xiàn)這個(gè)方法,所以動(dòng)態(tài)添加了這個(gè)方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run:age:)) {//第一步自己添加方法
class_addMethod(self, sel, (IMP)newRun, "v@:@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
這個(gè)添加一個(gè)自定義的方法
void newRun(id self,SEL sel, NSString *str, NSInteger age) {//自定義方法實(shí)現(xiàn)
NSLog(@"---run ok---%@---%ld",str,(long)age);
}
這里用到了runtime的動(dòng)態(tài)添加方法,不熟悉的可以看看這個(gè)系列文章的前幾篇
,執(zhí)行結(jié)果:
QFMessageForwardDemo[18572:704661] ---run ok---hello---18
前端轉(zhuǎn)發(fā)
如果第一步?jīng)]有動(dòng)態(tài)添加方法,則會(huì)進(jìn)入轉(zhuǎn)發(fā)的第二步,前端轉(zhuǎn)發(fā),所謂前端轉(zhuǎn)發(fā)即是,本類沒有實(shí)現(xiàn)這個(gè)方法,但是另外的一個(gè)類實(shí)現(xiàn)了這個(gè)方法,那么我們可以直接轉(zhuǎn)發(fā)這條消息到另外的類實(shí)現(xiàn)調(diào)用。這里我們新建QFOtherPerson```` 并且實(shí)現(xiàn)- (void)run:(NSString *)name age:(NSInteger)age```方法
- (void)run:(NSString *)name age:(NSInteger)age{
NSLog(@"%@ 執(zhí)行了 run name = %@,age = %ld",NSStringFromClass([self class]), name,age);
}
在QFPerson的實(shí)現(xiàn)文件中,實(shí)現(xiàn)下面的方法完成轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[QFOtherPerson alloc]init];//第二步,前端轉(zhuǎn)發(fā)
}
ps:此時(shí)要先注釋注釋掉第一步的動(dòng)態(tài)添加方法
執(zhí)行結(jié)果:
QFMessageForwardDemo[19629:739692] QFOtherPerson 執(zhí)行了 run name = hello,age = 18
簽名轉(zhuǎn)發(fā)
第三步,也是最后的機(jī)會(huì)處理這條消息了,首先生成方法簽名:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {//第三步,簽名轉(zhuǎn)發(fā)
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"run:age:"]) {//是我們需要轉(zhuǎn)發(fā)的run方法
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
} else {
return [super methodSignatureForSelector:aSelector];
}
}
關(guān)于invocation你熟悉的可以看ResponderChain+Strategy+MVVM實(shí)現(xiàn)一個(gè)優(yōu)雅的TableView這里面有完整的invocation調(diào)用方法的例子,這里就不做粘貼了。
最后調(diào)用-forwardInvocation:指定target,完成轉(zhuǎn)發(fā):
- (void)forwardInvocation:(NSInvocation *)anInvocation {//簽名轉(zhuǎn)發(fā)
SEL selector = [anInvocation selector];//目標(biāo)方法
QFOtherPerson *other = [[QFOtherPerson alloc]init];//轉(zhuǎn)發(fā)對(duì)象
if ([other respondsToSelector:selector]) {//目標(biāo)對(duì)象能相應(yīng)此方法
[anInvocation invokeWithTarget:other];
} else {
return [super forwardInvocation:anInvocation];
}
}
運(yùn)行結(jié)果:
QFMessageForwardDemo[31317:930989] QFOtherPerson 執(zhí)行了 run name = hello,age = 18
以上就是方法轉(zhuǎn)發(fā)的流程,如果以上三步都沒有實(shí)現(xiàn)則會(huì)崩潰,顯示一個(gè)很常見的異常- unrecognized selector sent to instance 0x60000001b440'
關(guān)于之前的流程請(qǐng)參考:Runtime源碼 方法調(diào)用的過程