iOS消息转发机制
2018-12-02 本文已影响42人
Eddiegooo
iOS方法调用实际上就是消息转发过程
最简单的方法调用:
[[MessageSend new] sendMessage:@"Hello"];
//等同于
//objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello");
开发过程中经常会遇到这个错误unrecognized selector sent to instance
; 没有实现方法,或是方法没有找到,就直接崩溃了。
实例:
调用一个方法:
[[MessageSend new] sendMessage:@"Hello"];
来看下具体怎么实现。
首先我不实现这个方法,来看下消息转发机制。
//实际上消息转发分为三个部分
//1.动态方法解析
//2.快速转发
//3.慢速转发
//4.消息未处理。
//越往下花费的代价越大。。
消息转发.png
1.动态方法解析
#import <objc/message.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *method = NSStringFromSelector(sel);
if ([method isEqualToString:@"sendMessage:"]) {
return class_addMethod(self, sel, (IMP)sendIMP, "v@:@");
}
return NO;
}
void sendIMP(id self, SEL _cmd, NSString *msg) {
NSLog(@"msg = %@", msg);
}
以上就是动态方法解析。
2.快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *method = NSStringFromSelector(aSelector);
if ([method isEqualToString:@"sendMessage:"]) {
//去找备胎类,看有没有实现这个方法
return [SpareWheel new];
}
return [super forwardingTargetForSelector:aSelector];
}
这时候涉及到了另外一个类,看它有没有实现对应的方法。
如果有实现,消息转发结束。
3.慢速转发
//封装方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *method = NSStringFromSelector(aSelector);
if ([method isEqualToString:@"sendMessage:"]) {
//把这个方法存起来
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//获得方法编号
SEL sel = [anInvocation selector];
//还来找备胎
SpareWheel *sp = [SpareWheel new];
//判断能否响应方法
if ([sp respondsToSelector:sel]) {
anInvocation.target = sp;
}else {
[super forwardInvocation:anInvocation];
}
}
如果备胎类或是哪都找不到对应的实现方法,就会到这个方法里
-(void)doesNotRecognizeSelector:(SEL)aSelector {
}
作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。
虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。
在release 模式下我们可以尝试不让它抛出异常,这样就可以避免找不到方法崩溃了。 但是debug、还没上线时千万别这么做
假如你一定要这么做,可以这么写个分类:
@implementation NSObject (Message)
- (void)doesNotRecognizeSelector:(SEL)aSelector {
if (DEBUG) {
NSLog(@"这里有方法没实现,但是我就不让他崩溃");
}
}
消息转发可以来兼容API。
NSInvocation 模块化、路由模式 解耦。
//消息转发三种方法:
//1. 直接调用
MessageSend *send = [MessageSend new];
[send sendMessage:@"Eddiegooo"];
//2。perform 方法
[send performSelector:@selector(sendMessage:) withObject:@"Eddiegooo"];
//但是多个参数怎么办呢? 用三方法
//3. NSInvocation
NSMethodSignature *methodSign = [send methodSignatureForSelector:@selector(sendMessage:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSign];
[invocation setTarget:send];
[invocation setSelector:@selector(sendMessage:)];
NSString *string = @"Eddiegooo";
[invocation setArgument:&string atIndex:2]; //这里为啥v从2开始? IMP(self, _cmd, *) 传参都是在第三个才是。
[invocation invoke];
NSInvocation 调用Block有点复杂,需要知道Block的底层源码。Aspects库参考。