前言
在 Objective-C 中,一个办法的调用,编译器会将其被转成 objc_msgSend
的方式,沿着这个目标或许类的承继链,顺次去查找是否有对应的办法完成,假如查找至根类,也便是 NSObject
都没有找到对应的办法,那么就会触发音讯转发流程。假如你在音讯转发流程,仍是没有进行处理,那么体系就会报错,程序停止。
这篇文章,只讲音讯转发流程。(符合单一职责的规划模式,dog head)
三板斧
三板斧其实都是模板办法,只要完成对应的模板办法即可。
第一斧:动态办法解析
首要,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 是一门动态语言的一个体现,究竟咱们能够在运行时随意的调整办法的调用。
三板斧耍完了,该挨打了。
链接
Message Forwarding (官方文档)