陈江川

邮箱:jiangchuanc@gmail.com

Runtime - 消息转发

消息转发过程

之前了解了objc_msgSend内部的实现,在没有找到对应SEL的IMP,Runtime会做如下几件事:

  1. 判断一次是否有动态添加方法,没有进入步骤2:

    (libobjc.A.dylib +[NSObject resolveClassMethod:] at NSObject.mm:2024)
    (libobjc.A.dylib +[NSObject resolveInstanceMethod:] at NSObject.mm:2028)
    

    如果自己没有实现该方法,那么会调用NSObject中的,实现如下:

    // 是否动态添加类方法
    + (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
    }
    // 是否动态添加对象方法
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    
  2. 重定向,消息转发之前,Runtime允许我们替换消息的接收者为其他对象
    forwardingTargetForSelector:方法,如果自己没有实现进入步骤3

    (libobjc.A.dylib -[NSObject forwardingTargetForSelector:] at NSObject.mm:2106)
    

    如果自己没有实现该方法,就会调用NSObject中的方法,实现如下:

    // 改变消息接收对象
    - (id)forwardingTargetForSelector:(SEL)sel {
        return nil;
    }
    
  3. 消息转发,分为两步骤,如果不处理,跳到步骤4:

    • 获取方法签名

      方法签名为NSMethodSignature对象,它记录了一个方法的返回值和参数。
      An NSMethodSignature object records type information for the return value and parameters of a method. It is used to forward messages that the receiving object does not respond to—most notably in the case of distributed objects.

    // 重写该方法
    // Replaced by CF (returns an NSMethodSignature)
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        _objc_fatal("-[NSObject methodSignatureForSelector:] "
                 "not available without CoreFoundation");
    }
    
    • 执行消息转发

      当获取到一个方法的签名后,系统会根据这个NSMethodSignature对象为我们初始化一个NSInvocation对象。下面是Apple对NSInvocation的解释:
      An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value. Each of these elements can be set directly, and the return value is set automatically when the NSInvocation object is dispatched.
      大致意思就是NSInvocation对象包含了一个消息的接收者、方法名、参数和返回值。

    // 重写该方法
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
    }
    
  4. 调用doesNotRecognizeSelector:方法,抛出异常

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

消息转发实现

方式一:动态添加方法,实现resolveInstanceMethod:

#import "ViewController.h"
#import <objc/runtime.h>

@interface JCPerson : NSObject

- (void)test;

@end

@implementation JCPerson

void dynamicMethodIMP(id self, SEL _cmd)
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(test)) {
          /*
            V代表void  @代表id类型  :代表方法选择器,也就是SEL
            void dynamicMethodIMP(id self, SEL _cmd)
           */
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "V@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    JCPerson *person = [[JCPerson alloc] init];

    // test方法只在JCPerson中声明没有实现
    [person test];
}

@end

方式二:重定向接收消息对象,实现forwardingTargetForSelector:方法

@interface JCStudent : NSObject

- (void)test;

@end

@implementation JCStudent

- (void)test {
    NSLog(@"%s", __func__);
}

@end

@interface JCPerson : NSObject

- (void)test;

@end

@implementation JCPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {

    if (aSelector == @selector(test)) {

        JCStudent *std = [[JCStudent alloc] init];
        return std;
    }

    return [super forwardingTargetForSelector:aSelector];
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    JCPerson *person = [[JCPerson alloc] init];
    [person test];
}

@end

方法三:消息转发,实现方法methodSignatureForSelector:

// 先获取方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    NSMethodSignature *signature = [super methodSignatureForSelector:sSelector];
    if (!signature) {
        JCStudent *std = [[JCStudent alloc] init];
        signature = [std methodSignatureForSelector:@selector(setPersonName: andAge:)];     
    }
    return signature;
}

// 执行消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 设置参数
    NSString *name = @"JC";
    int age = 18;
    
    // 为什么从2开始,因为0被self占用,1被SEL _cmd占用
    [anInvocation setArgument:&name atIndex:2];
    [anInvocation setArgument:&age atIndex:3];
    
    // 给NSInvocation绑定SEL
    anInvocation.selector = @selector(setPersonName: andAge:);
    
    JCStudent *std = [[JCStudent alloc] init];
    // 判断JCStudent是否实现了绑定的方法
    if ([std respondsToSelector:anInvocation.selector]) {
        // 绑定消息接收者
        [anInvocation invokeWithTarget:std];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

文中涉及的Demo

« idoubs编译笔记 Objective-C中的Block<三> »