最简单的方法调用:bash
[[MessageSend new] sendMessage:@"Hello"]; //等同于 //objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello"); 复制代码
开发过程当中常常会遇到这个错误unrecognized selector sent to instance
; 没有实现方法,或是方法没有找到,就直接崩溃了。 markdown
调用一个方法:模块化
[[MessageSend new] sendMessage:@"Hello"]; 复制代码
来看下具体怎么实现。函数
1.首先经过[Message new]对象的ISA指针找打它对应的class。 2.在class的method list 查找是否有sendMessage方法。 3.若是没有就去它的superclass里继续查找。 4.一旦查找到对应的函数方法,就去执行它的实现IMP。 5.若是一直没有找到,就执行消息转发。spa
首先我不实现这个方法,来看下消息转发机制。debug
//实际上消息转发分为三个部分 //1.动态方法解析 //2.快速转发 //3.慢速转发 //4.消息未处理。 //越往下花费的代价越大。。 指针
![]()
1.动态方法解析code
#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); } 复制代码
以上就是动态方法解析。orm
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。
//消息转发三种方法: //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库参考。