前言

Objective-C 中,一个办法的调用,编译器会将其被转成 objc_msgSend 的方式,沿着这个目标或许类的承继链,顺次去查找是否有对应的办法完成,假如查找至根类,也便是 NSObject 都没有找到对应的办法,那么就会触发音讯转发流程。假如你在音讯转发流程,仍是没有进行处理,那么体系就会报错,程序停止。

这篇文章,只讲音讯转发流程。(符合单一职责的规划模式,dog head)

三板斧

三板斧其实都是模板办法,只要完成对应的模板办法即可。

音讯转发.png

第一斧:动态办法解析

首要,Runtime 会去调用 +resolveInstanceMethod:(实例办法) 或 +resolveClassMethod:(类办法),让你有机会去动态的增加一个办法,回来 YES,就会重启音讯发送流程,再履行一次办法调用。

举个比如:

#import "ViewController.h"
#import <objc/runtime.h>
@interface Cat : NSObject
@end
@implementation Cat
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(fly)) {
        class_addMethod([self class], sel, (IMP)run, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
void run(id obj, SEL _cmd) {
    NSLog(@"Cat run");
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cat *cat = [[Cat alloc] init];
    [cat performSelector:@selector(fly)];
}
@end

测验去调用 Cat 类中的 fly 办法,可是 Cat 中并没有 fly 办法,所以运用 class_addMethod 去动态的增加了一个办法,sel 依然是 fly,可是 IMP 改成了 run 办法。sel 能够理解为办法的姓名,IMP 则是办法的完成,也便是这样处理之后,Cat 中被增加了一个名为 fly,可是完成却是 run 的办法,外界依然是经过 fly 来进行调用。

处理之后的打印成果:

Cat run

第二板斧:重定向

假如没有在第一步中,去动态的增加一个办法,那么就会履行到下一步,重定向,重定向意思是,你这没有这个办法,可是他人有,你让他人来完成这个办法。

比如:

#import "ViewController.h"
#import <objc/runtime.h>
@interface Bird : NSObject
@end
@implementation Bird
- (void)fly {
    NSLog(@"Bird fly");
}
@end
@interface Cat : NSObject
@end
@implementation Cat
//+ (BOOL)resolveInstanceMethod:(SEL)sel {
//    if (sel == @selector(fly)) {
//        class_addMethod([self class], sel, (IMP)run, "v@:");
//        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
//}
//
//void run(id obj, SEL _cmd) {
//    NSLog(@"Cat run");
//}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(fly)) {
        return [[Bird alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cat *cat = [[Cat alloc] init];
    [cat performSelector:@selector(fly)];
}
@end

输出是:

Bird fly

假如打开注释的代码,由于是先履行 resolveInstanceMethod: 办法,并且在其中增加的新的办法完成,所以不会走到 forwardingTargetForSelector:,输出是:

Cat run

不管 resolveInstanceMethod: 回来是 YES 仍是 NO

第三板斧:完好的音讯转发

最终一步像是一个那些不能被辨认的音讯的分发中心,它能够将这些音讯转发给不同的目标,也能够将一个音讯翻译成另外的一个音讯,或许简单的吃掉某些音讯,因而没有响应也没有错误,不会导致你的程序 crash。它也能够对不同的音讯供给相同的响应,这一切都取决于办法的具体完成,该办法供给的是将不同目标连接到音讯链的才能。

主要是两个办法,一个办法是 +(NSMethodSignature *)methodSignatureForSelector:,这个办法用来获取一个适宜的办法签名,咱们之前增加办法时用到的 v@: 便是一个办法的签名,具体的签名规则,能够看一下苹果的文档 Type Encodings,有很具体的说明。

假如要触发最终一个办法,这个签名有必要是正确的函数签名,假如回来的是 nil,Runtime 会直接宣布 -doseNotRecognizeSelector: 音讯,程序直接 crash。

最终一个办法,便是 forwardInvocation: 办法,咱们直接看比如:

#import "ViewController.h"
#import <objc/runtime.h>
@interface Bird : NSObject
@end
@implementation Bird
- (void)fly {
    NSLog(@"Bird fly");
}
@end
@interface Cat : NSObject
@end
@implementation Cat
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(fly)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    Bird *bird = [[Bird alloc] init];
    if ([bird respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:bird];
    }
    else {
        [super forwardInvocation:anInvocation];
    }
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cat *cat = [[Cat alloc] init];
    [cat performSelector:@selector(fly)];
} 
@end

这里的处理,仍是将音讯转给 Bird 类去处理,可是你也能够做其他的完成,或许简单点,什么都不完成(不推荐),那么履行也不会报错。

总结

其实音讯转发的流程是简单的,究竟都有对应的模板办法,顺次调用即可,这也是 Objective-C 是一门动态语言的一个体现,究竟咱们能够在运行时随意的调整办法的调用。

三板斧耍完了,该挨打了。

程咬金.jpeg

链接

Message Forwarding (官方文档)