老规矩,先上经典图
![](https://img.haomeiwen.com/i6206716/4ba56ab075bc8f9e.png)
消息在继承树上没有找到实现还有消息转发提供三次机会,不至于直接报doesNotRecognizeSelector错误崩溃,当然这也给线上热修复提供了支持。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(drink)];
}
继承树上没有drink方法,则进入消息转发阶段
一、动态方法解析 resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"drink"])
{
NSLog(@"drink 动态方法解析");
class_addMethod([self class], sel, (IMP)drinkMethod, nil);
return YES;
}
return [super resolveInstanceMethod:sel];
}
void drinkMethod()
{
NSLog(@"动态方法添加 drink");
}
通过runtime动态添加drinkMethod方法来实现,这个类方法只要动态增加了方法,不管return YES或者NO都不会继续往下走。
二、备用接受者
如果resolveInstanceMethod没有动态添加方法就会继续往下走forwardingTargetForSelector,将消息转发给其他对象来实现。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"drink"])
{
NSLog(@"备用接收者");
return [Son new];
}
return [super forwardingTargetForSelector:aSelector];
}
如果Son类中有drink方法,则会执行,如果没有drink则会报doesNotRecognizeSelector崩溃。如果forwardingTargetForSelector方法返回nil或者没有实现,则会进入最后一步完整消息转发。
3、完整消息转发
如果前两步都未处理,则会进入最后的机会,先调用methodSignatureForSelector获取方法签名,每个字符都代表一种参数,具体对应表格可参考官方文档方法签名,也可以直接使用类型编码 @encode(type) 获取表示该类型的字符串,而不必硬编码。然后调用forwardInvocation处理,这一步的处理可以直接转发给其它对象,即和第二步的效果一样,没有必要,故在这一步,会改变消息的内容,比如增加参数,改变方法名称。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"drink"])
{
NSLog(@"签名 进入forward");
return [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = @selector(water:);
NSString *str = [NSString stringWithFormat:@"%s%s%s%s",@encode(void),@encode(id),@encode(SEL),@encode(char *)];
const char * cStr = [str cStringUsingEncoding:NSUTF8StringEncoding];//等同于"v@:*"
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:cStr];
anInvocation = [NSInvocation invocationWithMethodSignature:signature];
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(water:)];
NSString *water = @"喝水";
[anInvocation setArgument:&water atIndex:2];//第三个参数是字符串
if ([self respondsToSelector:sel])
{
[anInvocation invokeWithTarget:self];
return;
}else
{
Son *s = [Son new];
if ([s respondsToSelector:sel])
{
[anInvocation invokeWithTarget:s];
return;
}
}
[super forwardInvocation:anInvocation];
}
- (void)water:(NSString *)str
{
NSLog(@"完整消息转发 %@",str);
}
通过第三步完整消息转发我们发现drink方法已经转发到了water:方法里,其中"v@:"里,v代表返回值是void,@代表响应者是一个ID对象 ,:代表选择子,代表字符串,我们这里water:方法改变了方法名称,增加了字符串参数。如果还是找不到对应方法,则会沿着继承树查找,最终还是没找到就会报doesNotRecognizeSelector错误程序挂掉。
网友评论