Runtime系列之消息转发

作者: 岁与禾 | 来源:发表于2017-08-23 23:28 被阅读43次

    Runtime系列之消息转发

    1 消息传递

    OC属于动态类型语言,方法的调用是通过发送消息来完成的。objc/runtime.h头文件中包含了objc_send()函数,通过该函数对某个对象发送消息。

    进入头文件中查看,objc_send()方法的原型如下

    void objc_msgSend(void /* id self, SEL op, ... */ )
    

    函数中隐藏了两个参数,一个是self,一个是op。

    id self表示给当前哪个对象发送消息,也就是哪个对象调用了该方法。

    SEL op表示当前调用的方法名称 @selector(methodName)

    示例:

    - (void)test
    {
        NSMutableArray *array = [NSMutableArray array];
        [array addObject:@"123"];
        
        //将系统的objc_msgSend()函数强制转换,方便调用
        void(*array_msgSend)(id self, SEL sel, id value, int index) = (void *)objc_msgSend;
        
        //发送消息(相当于直接调用方法[array insertObject:@"345" atIndex:0])
        array_msgSend(array, @selector(insertObject:atIndex:), @"345",0);
        
        //打印验证
        NSLog(@"%@",array);
    }
    
    

    2 动态方法解析和消息转发

    当使用msg_send()函数传递消息的时候,经常会碰到当前对象并没有当前的SEL,就会抛出异常 unrecgnized selector send to ...。但是在抛出异常之前,Objective-C的运行时会有三次来找到对应的方法的机会。

    • Method Resolution
    • Fast Forwarding
    • Normal Forwarding

    依次进行上面三种方式。

    2.1 方法解析(Mehod Resolution)

    在类的方法列表中找不到即将调用的方法时,会判断当前类是否实现了+(BOOL)resolveInstanceMethod:(SEL)sel方法和+(BOOL)resolveClassMethod:(SEL)sel方法。

    可以在上面两个方法中,进行动态的方法解析,设置当前消息的具体函数体实现。

    实例:

    /**
        前提:当前控制器不实现 -(void)myMethod:(NSString *)str方法
        步骤:
            1 当前控制器实现自定义函数 void myFunction(id self, SEL sel, NSString *str)
            2 调用未实现的方法 [self performSelector:@selector(myMethod:) withObject:@"哈哈"]
            3 通过resolveInstanceMethod:方法来达到调用函数体myFunction的效果
    */
    - (void)viewDidLoad
    {
        [self performSelector:@selector(myMethod:) withObject:@"哈哈哈"];
    }
    
    //实现动态方法解析
    + (BOOL)resolveInstanceMehod:(SEL)sel
    {   
        //如果当前的sel是myMethod的话,将当前方法的绑定到函数体myFunction上
        if(sel == @selector(myMethod))
        {
            //动态添加当前方法,并绑定函数体
            //"v@:@" v表示函数体返回值void,@表示参数类型id, :表示参数类型SEL, @表示参数类型NSString
            class_addMethod(self.class, self, (IMP)myFunction, "v@:@");
            
            //返回YES,就表示执行当前绑定的方法,不用抛出异常了
            return YES;
        }
        //调用父类的方法
        return [super resolveInstanceMethod:sel];
    }
    
    //自定义的函数myFuntion
    void myFunction(id self, SEL sel, NSString *str)
    {
        NSLog(@"hello myFunction -> %@", str);
    }
    
    
    

    2.2 快速消息转发(Fast Forwarding)

    如果上面的方法+resolveInstanceMethod:或者+resloveClassMethod:的返回值为NO,或者没有实现。则runtime会继续判断当前类是否实现了-forwardingTargetForSelector:方法。

    - forwardingTargetForSelector:

    示例:

    /**
        实现的效果是:
        
        当前对象调用myMethod方法: [self myMethod:@"哈哈"] 
        实际上是self.alterObject调用的该方法: [self.alterObject myMethod:@"哈哈"]
    */
    
    @interface ViewController()
    
    //某一个对象
    @property(nonatomic, strong) id alterObject;
    
    @end
    
    @implemention
    
    - (void)viewDidLoad
    {
        //调用不存在的方法 -myMethod:
        [self performSelector:@selector(myMethod:) withObject:@"哈哈哈"];
    }
    
    //实现消息转发,用新的对象self.alterObject调用 -myMethod:方法
    - (id)forwardingTargetForSelector:(SEL)sel
    {
        //如果当前调用的是 myMethod方法,用新的对象来调用当前的方法
        if(sel == @selector(myMethod:))
        {
            return self.alterObject;
        }
        
        return [super forwardingTargetForSelector:sel];
    }
    
    @end
    
    

    2.3 普通消息转发(Normal Forwarding)

    上面的快速转发,如果没有实现,或者返回值为空,则runtime会尝试调用-methodSignatureForSelector:方法来获取一个方法签名。

    • 如果返回的方法签名为nil,则直接抛出异常,程序停止。
    • 如果返回的方法签名存在,则runtime会调用-forwardInvocation:方法,则当前方法中实现消息转发。

    实例:

    @interface ViewController()
    
    //某一个对象
    @property(nonatomic, strong) id alterObject;
    
    @end
    
    @implemention
    
    - (void)viewDidLoad
    {
        //调用不存在的方法 -myMethod:
        [self performSelector:@selector(myMethod:) withObject:@"哈哈哈"];
    }
    
    //返回方法签名,如果为nil,则直接抛出异常;反之,调用forwardInvocation方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if(aSelector == @selector(myMethod:))
        {
            NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
            return sign;
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    //前向回调,在当前方法中处理(转发消息)
    - (void)forwardInvocation:(NSInvocation)anInvocation
    {
        //取出当前为实现的SEL
        SEL sel = anInvocation.selector;
        
        if([self.alterObject respondsToSelector:sel])
        {
            //将消息转发给self.alterObject
            [anInvocation invokeWithTarget: self.alterObject]; 让self.alterObject对象调用sel方法
        }else{
            //直接调用系统的 - doesNotRecognizeSelector:方法,抛出异常
            [self doesNotRecognizeSelector:sel];
        }
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:Runtime系列之消息转发

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