您的当前位置:首页正文

ios — 消息转发

来源:花图问答

概念

首先我们需要简单了解下,什么是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。

  1. 方案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

以上方法,我们可以得出,我们把程序运行到创建的备用类的方法中,实现了消息转发。

  1. 方案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.细节处理,抛出异常