美文网首页iOS Developer
iOS 消息转发:这个锅谁背

iOS 消息转发:这个锅谁背

作者: Allan_野草 | 来源:发表于2017-03-21 16:52 被阅读108次

    一、片头

    在Objective-C中,方法调用可以说成是消息发送。向一个对象发送任意一条消息都是可以的,即使类中没有实现该消息(方法)。
    比如下面的rev的类中没有willCrash这个方法,向对象rev发送消息

    [rev performSelector:@selector(willCrash)]
    

    objc_msgSend(rev,@selector(willCrash))
    

    当执行到上面的这句话时,显然会发生崩溃。
    (抛出 unrecognized selector sent to instance。)

    好在,像这种情况,苹果贴心地提供了一种“补全”机制,
    也叫“消息转发”机制,可以让编程者有机会处理崩溃。

    消息转发,通俗来讲就是找人背锅。主要走的流程,是重写NSObject的三个方法:
    1、自己试下补锅:+resolveInstanceMethod:(要处理静态方法就是+resolveClassMethod:)
    2、让别人来背锅:-forwardingTargetForSelector:
    3、还是自己解决吧:-(void)forwardInvocation:(NSInvocation *)anInvocation

    二、详解

    举个例,Person类,类中只有-run方法

    // Person.h
    @interface Person  : NSObject
    - (void)run;
    @end 
    
    // Person.m
    @implememtation
    - (void)run {
      NSLog(@"run");
    }
    @end
    

    向这个类对象调用一个不存在的方法fly

    Person *man = [Person new];
    [man performSelector:@selector(fly)]; // fly并没有相应实现
    

    下面来看看,怎么用“消息转发”,从而让它不崩溃。

    1、resolveInstanceMethod(自己试下补锅)

    首先来重写Person类的-resolveInstanceMethod:方法,打个断点,可以看到在程序挂掉之前该方法被回调

    + (BOOL)resolveInstanceMethod:(SEL)sel {
      BOOL didResolve;
      // do something ..
      return didResolve;
    }
    
    • 无论该方法return YES或者NO,都不影响转发行为。虽然不清楚return的作用,反正结果就是没什么卵用
    • 总之在这个方法结束之前,动态实现fly方法。下面是让@selector(fly)指向@selector(getTwoWings)的实现(其实可以这么理解,@selector(fly)和@selector(getTwoWings)都是一个符号,对应指向的都是同一个实现)
    @implememtation
    + (BOOL)resolveInstanceMethod:(SEL)sel {
         if (sel==@selector(fly)) NSLog(@"try fly");
    
         // 获得方法对象
         Method impMethod = class_getInstanceMethod(self, @selector(getTwoWings));
         // 为fly方法添加getTwoWings的实现
         class_addMethod(self, @selector(fly), method_getImplementation(impMethod), method_getTypeEncoding(impMethod));
    
         // return YES NO都一样
         return YES;
    }
    - (void) getTwoWings {
          NSLog(@"get two wings and i will fly");
    }
    @end
    
    // 不会发生崩溃,控制台打印 try fly
    // 控制台打印 get two wings and i will fly
    [man performSelector:@selector(fly)];
    

    如果不给fly动态实现,接下来会进入“转发”

    2、forwardingTargetForSelector(让别人来背锅)

    “转发”会回调forwardingTargetForSelector:
    把该消息转发给某个对象来处理:

    - (id)forwardingTargetForSelector:(SEL)aSelector {
          id handler; // 可以处理该消息的对象
          return handler;
    }
    
    • 若return为nil,不转发消息,后续仍然是自己来处理
    • 若return不为nil,由对象handler去回调处理这个消息。也就是相当于
    [hander performSelector:@selector(fly)];
    // 实现上是 IMP ptr = class_getMethodImplementation([handler class], @selector(fly));
    // if ptr == NULL {让handler处理,转发到此结束}else{还有下一步}
    

    3、forwardInvocation(还是自己解决吧)

    回调Person的-(void)forwardInvocation:(NSInvocation *)anInvocation

    -(void)forwardInvocation:(NSInvocation *)anInvocation {
         if (anInvocation.selector==@selector(fly)) {
              // 相应处理,消息转发完成
         }
    }
    

    三、测试例子

    实现以下的场景:
    向person发送fly消息,实际上让另一个对象去调用它的fly方法

    重写-forwardingTargetForSelector:,把fly消息转发给Plane类对象去处理

    // Person类
    @implememtation
    + (BOOL)resolveInstanceMethod:(SEL)sel {
         if (sel==@selector(fly)) NSLog(@"try fly");// 没有实现-fly,那么消息将转发
         return NO;
    }
    -(id)forwardingTargetForSelector:(SEL)aSelector {
          Plane *plane= [Plane new];
          return plane;// 转发给plane对象
    }
    @end
    

    Plane是另外一个类:

    // - Plane.h
    @interface Plane : NSObject
    @end 
    // - Plane.m
    @implememtaion Plane
    -(void)fly {
          NSLog(@"take a plane and i will fly");
    }
    @end
    

    测试结果

    Person *man = [Person new];
    [man performSelector:@selector(fly)]; 
    // 控制台打印 try fly
    // 控制台打印 take a plane and i will fly
    

    相当于调用了[plane fly];

    片尾

    当向一个对象发送一个它不能处理的消息/方法,苹果会不断地询问你,去动态实现这个方法,简而言之,消息转发就是这么一回事。

    实际应用上,消息转发配合上Runtime也有很多玩法。比如。。就不比如了,哪天写一篇文章,先挖个坑吧~

    再至于流程3的forwardInvocation中,NSInvocation怎么个用法?请看另外下面这篇文章,拉到最底就能看到了
    NSInvocation:iOS 不走寻常路:调用方法的6种姿势,你知道几种

    End

    相关文章

      网友评论

        本文标题:iOS 消息转发:这个锅谁背

        本文链接:https://www.haomeiwen.com/subject/atbkmttx.html