经典错误再现
#import "ViewController.h"
#import "TestObject.h"
@interface ViewController ()
@property (nonatomic, strong) TestObject *testObj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.testObj = [TestObject new];
[self.testObj performSelector:@selector(testFunction)];
}
@end
调用了不存在的方法导致崩溃报错,报错信息如下:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject testFunction]: unrecognized selector sent to instance
消息转发机制
从全局来看,消息转发机制共分为3大步骤:
1. Method resolution 方法解析处理阶段
2. Fast forwarding 快速转发阶段
3. Normal forwarding 常规转发阶段
1、Method resolution 方法解析处理阶段
如果调用了对象方法会先调用resolveInstanceMethod函数进行判断。如果返回YES代表能接收消息;如果返回NO代表不能接收消息,则进入第二步处理。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"testFunction"]) {
return NO;
}
return [super resolveInstanceMethod:sel];
}
2、Fast forwarding 快速转发阶段
从第一步进来的代表自身不能响应消息,需要返回一个可以响应消息的实例对象(新建了BackupTestObject类,并声明和实现了testFunction方法)。
如果返回了实例对象,则转发SEL到此对象;如果返回nil,咋进入第三步。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"testFunction"]) {
return [BackupTestObject new];
}
return [super forwardingTargetForSelector:aSelector];
}
3、Normal forwarding 常规转发阶段
如果第2步返回nil,则说明没有可以响应的实例对象,则进入第3步。
第3步的消息转发机制需要返回一个可以响应的对象,并手动将消息转发给响应对象。
实现步骤如下
一、对SEL消息进行签名
通过实现methodSignatureForSelector
返回SEL消息签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([super methodSignatureForSelector:aSelector] == nil) {
BackupTestObject *backupTestObj = [BackupTestObject new];
NSMethodSignature *sign = [backupTestObj methodSignatureForSelector:aSelector];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
二、进行消息传递处理
如果上一步SEL消息签名不为空,则进入消息传递处理,把消息转发给可响应对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
BackupTestObject *backupTestObj = [BackupTestObject new];
SEL sel = anInvocation.selector;
if ([backupTestObj respondsToSelector:sel]) {//消息可被响应
[anInvocation invokeWithTarget:backupTestObj];
} else {//无可响应对象,抛异常
[self doesNotRecognizeSelector:sel];
}
}