美文网首页iOS常见问题
iOS 开发:Runtime(详解二)消息转发

iOS 开发:Runtime(详解二)消息转发

作者: 哈布福禄克 | 来源:发表于2019-11-06 10:09 被阅读0次

Runtime基础知识的 2. 消息机制的基本原理 最后一步中我们提到:若找不到对应的 selector,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃。

当一个方法找不到的时候,Runtime 提供了 消息动态解析、消息接受者重定向、消息重定向 等三步处理消息,具体流程如下图所示:

消息转发.png

1、 消息动态解析

1)主要用的的方法如下:
// 类方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;

/** 
 * class_addMethod    向具有给定名称和实现的类中添加新方法
 * @param cls         被添加方法的类
 * @param name        selector 方法名
 * @param imp         实现方法的函数指针
 * @param types imp   指向函数的返回值与参数类型
 * @return            如果添加方法成功返回 YES,否则返回 NO
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                const char * _Nullable types);
2)举个例子:
#import "ViewController.h"
#include "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 执行 fun 函数
    [self performSelector:@selector(fun)];
}

// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(fun)) { // 如果是执行 fun 函数,就动态解析,指定新的 IMP
        class_addMethod([self class], sel, (IMP)funMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void funMethod(id obj, SEL _cmd) {
    NSLog(@"funMethod"); //新的 fun 函数
}

@end

注释:我们注意到 class_addMethod 方法中的特殊参数 v@:,具体可参考官方文档中关于 Type Encodings 的说明:传送门

2、 消息接受者重定向

1)其中用到的方法:
// 重定向类方法的消息接收者,返回一个类或实例对象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者,返回一个类或实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector;

注意:
这里+resolveInstanceMethod: 或者 +resolveClassMethod:无论是返回 YES,还是返回 NO,只要其中没有添加其他函数实现,运行时都会进行下一步。

2)举个例子:
// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(fun)) {
        return [[Person alloc] init];
        // 返回 Person 对象(有实现fun方法),让 Person 对象接收这个消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

3、 消息重定向

1)用到的方法:
// 获取类方法函数的参数和返回值类型,返回签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

// 类方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;

// 获取对象方法函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

// 对象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
2)举个例子:
// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;   // 从 anInvocation 中获取消息
    
    Person *p = [[Person alloc] init];

    if([p respondsToSelector:sel]) {   // 判断 Person 对象方法是否可以响应 sel
        [anInvocation invokeWithTarget:p];  // 若可以响应,则将消息转发给其他对象处理
    } else {
        [self doesNotRecognizeSelector:sel];  // 若仍然无法响应,则报错:找不到响应方法
    }
}

额外话题
既然 -forwardingTargetForSelector: 和 -forwardInvocation: 都可以将消息转发给其他对象处理,那么两者的区别在哪?
区别就在于 :
-forwardingTargetForSelector: 只能将消息转发给一个对象
-forwardInvocation: 可以将消息转发给多个对象

相关文章

网友评论

    本文标题:iOS 开发:Runtime(详解二)消息转发

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