概念
首先我们需要简单了解下,什么是oc的消息以及oc中的消息转发。
先看一段简单的代码
person * p1 = [[person alloc]init];//创建person对象p1
[p1 run];//p1调用run方法
oc中调用方法就是向对象发送消息,上篇代码也就是给p1这个对象发送run消息。
声明方法.png
声明了run方法,却没有实现这个方法,以上我们在person类中的做法,则会出现我们经常碰到的crash。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[person run]: unrecognized selector sent to instance 0x60c00001dd30'
我们调用run方法时,系统会查看我们是否在该类中实现该方法,找不到该方法出现crash。
尽管我们如此任性,苹果爸爸还是为我们提供了一些方案,给我们补救,我们使用提供的这些方法进行消息转发,防止crash。
方案
我们先看下oc中,顶级基类NSObject为我们提供的解决方案
消息转发方案.png
1.方案1 —— 动态解析,添加方法
1.1我们先来实现+ (BOOL)resolveInstanceMethod:(SEL)这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod方法 sel = %@",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
实现此方法后,系统不会立即crash,会执行到这个方法中。
方案1.png
上述方法,让程序执行到这里,还是没能够解决问题。我们需要在此方法为程序动态添加一个新的方法。
1.2 动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod方法 sel = %@",NSStringFromSelector(sel));
// 判断有没有实现方法, 那么我们就是动态添加一个方法
if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)newRun, "你好");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//动态添加的方法 添加时候会运行到这里,程序不会crash 2
void newRun(id self,SEL sel,NSString *str) {
NSLog(@"动态添加的方法---runok---%@",str);
}
我们可以看到程序并没有崩溃。
1.2�方法.png
打印数据
2018-03-26 18:34:41.306929+0800 testCode[10348:644212] resolveInstanceMethod方法 sel = run
2018-03-26 18:34:41.307068+0800 testCode[10348:644212] 动态添加的方法---runok---(null)
通过以上的动态添加方法,我们可以看到,在没有实现调用方法时,程序并不会crash。
-
方案2 —— 消息转发重定向
2.1. 新建一个备用类,用来接收消息转发,并在该类中同样实现run方法
屏幕快照 2018-03-26 下午6.43.31.png
2.2 我们在该类中实现forwardingTargetForSelector方法,并返回备用类创建的对象
// 需要在转发的类中,实现同样的方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"aSelector -- %@",NSStringFromSelector(aSelector));
// return [super forwardingTargetForSelector:aSelector];
return [[newPerson alloc]init];
}
运行程序,同样也没有出现crash。
屏幕快照 2018-03-26 下午6.46.52.png
打印数据
2018-03-26 18:42:57.450757+0800 testCode[10445:652473] aSelector -- run
2018-03-26 18:42:57.450953+0800 testCode[10445:652473] newPerson go run
以上方法,我们可以得出,我们把程序运行到创建的备用类的方法中,实现了消息转发。
- 方案3 —— 生成方法签名转发消息
3.1 生成方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
{
// 转换字符
NSString * sel = NSStringFromSelector(aSelector);
// 判断,手动生成签名
if([sel isEqualToString:@"run"])
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
else
{
return [super methodSignatureForSelector:aSelector];
}
}
3.2 拿到消息转发签名,进行消息转发
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
// 取到消息
SEL seletor = [anInvocation selector];
// 新建需要咋混发的对象
newPerson * newP = [[newPerson alloc]init];
if ([newP respondsToSelector:seletor]) //判断是否实现
{
// 调用消息 进行转发
return [anInvocation invokeWithTarget:newP];
}
else
{
// 拿到签名
return [super forwardInvocation:anInvocation];
}
}
执行程序
屏幕快照 2018-03-26 下午6.54.09.png
我们在这个方法中,需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
补充
1.我们在备用类中,也没有实现run方法,我们可以添加以下方法中,在该方法中对用户进行相应的提示操作
-(void)doesNotRecognizeSelector:(SEL)aSelector
{
NSString * seletor = NSStringFromSelector(aSelector);
NSLog(@"您调用的方法不存在%@",seletor);
}
2.我们没有使用上述三个方案的任意一个,只添加了doesNotRecognizeSelector:(SEL)aSelector方法,运行程序,会定位到crash的位置。
屏幕快照 2018-03-26 下午6.59.31.png
总结
以上解决方案,会按照顺序执行,需要单独使用,如果第一个方案没实现的话,则继续执行第二个方案。
总结以下
1.动态方法解析
2.消息转发定向
3.生成方法签名
4.拿到签名,转发消息
5.细节处理,抛出异常