由于Objective-C是动态语言,在代码中如果是通过[object method] 形式实现向object发送method消息,如果object无法响应该消息,则编译器会报错,但如果通过[object performSelector:@selector(method)]这类方式,编译器就不会做拦截,只有等到运行时才能确定object是否能响应此消息,若是发现无法响应则会导致程序崩溃,一般抛出(unrecognized selector sent to instance)错误。
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(testMethod)];
}
2018-03-14 09:50:33.535715+0800 Nunca[7238:216397] -[NcTestThreeVCtr testMethod]: unrecognized selector sent to instance 0x7feaf3c2fb30
2018-03-14 09:52:27.793877+0800 Nunca[7238:216397] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NcTestVCtr testMethod]: unrecognized selector sent to instance 0x7feaf3c2fb30'
testMethod并未在类中实现,所以程序崩溃。
下面试图通过runtime中消息机制来对对象无法响应的消息进行动态绑定或转发,在程序调用doesNotRecognizeSelector方法前进行拦截。
一、.动态绑定
1.通过resolveInstanceMethod:(一般用于实现@dynamic属性)
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(testMethod)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (![self respondsToSelector:sel] ) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP (id self, SEL _cmd) {
NSLog(@"%@----", NSStringFromSelector(_cmd));
}
2018-03-14 09:52:36.409159+0800 Nunca[883:10041] testMethod----
testMethod并未在类中实现,但可以通过动态绑定为此方法绑定实现地址dynamicMethodIMP(IMP是一个函数指针,指向对应方法的实现部分),类中所有未实现方法的调用都可以通过此方法进行拦截,防止崩溃。
二、消息转发
1.通过forwardingTargetForSelector (可实现多重继承)
@interface NcTestClass ()
@end
@implementation NcTestClass
- (void)testMethod {
NSLog(@"testMethod in NcTestClass----");
}
@end
@interface NcTestVCtr ()
@end
@implementation NcTestVCtr
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(testMethod)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"testMethod"] ) {
NSLog(@"forwarding selector to NcTestClass----");
return [[NcTestClass alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
2018-03-14 10:52:33.267644+0800 Nunca[2400:43738] forwarding selector to NcTestClass----
2018-03-14 10:52:33.268506+0800 Nunca[2400:43738] testMethod in NcTestClass----
testMethod并未在NcTestVCtr中实现,NcTestVCtr通过forwardingTargetForSelector方法将testMethod转发至NcTestClass,最终NcTestClass中的testMethod被执行
forwardingTargetForSelector无法操作转发过程中的参数与返回值
2.通过forwardInvocation: (完整消息转发)
在调用forwardInvocation: 方法之前系统会先调用methodSignatureForSelector:方法,若methodSignatureForSelector: 返回值为nil 则会触发doesNotRecognizeSelector抛出异常然后crash,只有当返回签名不为空才会继续调用forwardInvocation: 进行消息转发
@interface NcTestClass ()
@end
@implementation NcTestClass
- (void)testMethod {
NSLog(@"testMethod in NcTestClass----");
}
@end
@interface NcTestThreeVCtr ()
@end
@implementation NcTestThreeVCtr
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(testMethod)];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([NcTestClass instancesRespondToSelector:aSelector]) {
signature = [NcTestClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation ----");
}
2018-03-14 14:17:56.303144+0800 Nunca[6479:168308] forwardInvocation ----
此时程序不崩溃(若forwardInvocation:方法未在类中实现则会导致崩溃),但由于只进行了签名 并未进行转发,所以NcTestClass中的testMethod也不会被执行
修改forwardInvocation:中的代码:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation ----");
if ([NcTestClass instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[NcTestClass alloc] init]];
}
}
打印运行结果:
2018-03-14 14:25:50.562469+0800 Nunca[6606:175681] forwardInvocation ----
2018-03-14 14:25:50.562643+0800 Nunca[6606:175681] testMethod in NcTestClass----
此时消息转发成功
以上都是针对实例方法的消息绑定与转发,针对类方法也有相对应的绑定与转发方法,大体相同,就不一一罗列了。