美文网首页
iOS运行时消息转发

iOS运行时消息转发

作者: MiniCoder | 来源:发表于2020-03-08 18:30 被阅读0次

    iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法;或者,还有另外的方法,由于Objective-C是一门动态语言,我们也可以在运行期再给类添加该方法,一样可以解决该问题,而这就涉及了类的消息转发机制。

    消息转发到底是什么呢? 这里将分为三个部分进行逐一讲解:
    1、动态方法解析
    2、备用接收者
    3、完整消息转发


    截屏2020-03-0819.01.00.png

    1.动态方法解析
    首先,Objective-C运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。
    如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。

    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"未实现的调用方法  = %@",NSStringFromSelector(sel));
        IMP class = class_getMethodImplementation(self, @selector(functionOne));
        class_addMethod(self, sel, class, "v@:");
        return true;
    }
    
    -(void)functionOne{
        NSLog(@"这是方法一");
    }
    

    我们可以在resolveInstanceMethod 中添加方法并返回YES,上例中我添加了functionOne并成功执行了,如果未添加方法实现,则返回false,切记不能在该方法中添加未实现的方法,否则会进入死循环。(实践中如果添加了方法,无论返回ture或者false都会执行添加的方法,不会进入消息转发,返回true或false似乎没什么影响,不过还是按照官方文档来做吧)。那么消息将进入到消息转发forwardingTargetForSelector中。
    添加方法中应该看到了v@:,这是定义方法的,我会写一篇文章专门讲解这个。

    2.备用接收者
    如果我们在resolveInstanceMethod没有执行添加的方法,并且返回了false,那么消息将会转发到这里。

    ///消息转发,如果在这里转发只可转发给一个对象或一个方法
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        NSLog(@"消息转发");
    //    return self.control;
        return  nil;
    }
    

    我们需要传递一个对象来接收这个方法信息。分为两种:
    1.是传递方法的接收者,那么消息将传递给该对象并查找对应的方法,我们定义的方法名称和参数个数必须相同。返回值可以不同。
    2.如果传为nil,那么消息将进入到完整消息转发中。

    3.完整消息转发
    如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
    消息转发分为两部分

    //注册方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    
    /// 消息转发,可转发给多个对象,或者多个方法同时执行
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    

    首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
    如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象

    ///注册方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        return [NSMethodSignature signatureWithObjCTypes:"#@:#"];
    }
    

    这里我定义的参数签名为"#@:#",接收一个NSString类型的参数,返回一个NSString类型的返回值。

    /// 消息转发,可转发给多个对象,或者多个方法同时执行
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%@",anInvocation);
        [anInvocation invokeWithTarget:self.control];
    }
    

    这里我们将消息转为控制器对象去接收处理。

      NSString * value =  [person performSelector:@selector(run:) withObject:@"这是参数"];
      NSLog(@"返回值 = %@",value);
    
    -(NSString * )run:(NSString * )value{
        
        NSLog(@"转发给控制器调用 %@ %s",value,__FUNCTION__);
        return @"这是返回值";
    }
    

    这里可以看到结果

     RunTime-iOS[27980:1126582] 转发给控制器调用 这是参数 -[ViewController run:]
     RunTime-iOS[27980:1126582] 返回值 = 这是返回值
    

    控制器方法可以接收方法参数,也可以返回参数给最初的调用者。

    相关文章

      网友评论

          本文标题:iOS运行时消息转发

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