先上一bug:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HomeViewController handleMessage]: unrecognized selector sent to instance 0x1635cf6e0'
这个bug是大家的老朋友了,就不介绍了!它是怎么产生的呢?
以HomeViewController为例,当HomeViewController不响应handleMessage消息时,则会去寻找它的父类,如果父类还没有,就会去找找寻它的父父类,直到NSObject。如果此时还未响应,消息分发机制就启动了,会依次调用下面的两个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)doesNotRecognizeSelector:(SEL)aSelector
首先调用的methodSignatureForSelector:
会返回一个空的方法签名,之后会走到doesNotRecognizeSelector:
抛出一个致命异常,于是程序崩溃!
在刚才的分析中,由于系统最终未能成功响应handleMessage
消息而崩溃。其实,在NSObject之后,系统抛出异常之前,我们是有机会去做一些处理的。这里一共有三次时机供我们选择,根据UIViewController的生命周期,我来依次介绍以下三种方法。
先来看一下流转图
方法一:动态添加方法
// 对应实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
// 对应类方法
+ (BOOL)resolveClassMethod:(SEL)sel
以HomeViewController为例
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(handleMessage)) {
class_addMethod([self class], sel, (IMP)surrogateHandleMessage, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void surrogateHandleMessage (id self, SEL _cmd) {
NSLog(@"l am a surrogate method of handleMessage");
}
通过拦截并重写resolveInstanceMethod:
方法,动态地添加方法surrogateHandleMessage
,去实现handleMessage
。
相应的,若是类方法,则拦截并重写resolveClassMethod:
。
方法二 将消息转发给其它对象
- (id)forwardingTargetForSelector:(SEL)aSelector
可以这样实现
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [SurrogateViewController new];
}
这里初始化SurrogateViewController的一个实例,然后由这个实例去响应handleMessage
消息。不过前提是SurrogateViewController有handleMessage
方法并实现了它。
如果上面两种方法都没使用,那么你仍有机会,可以在消息分发的实现中去处理。
方法三 消息分发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
先实现methodSignatureForSelector:
方法,获取一个方法签名,然后在forwardInvocation:
中进行消息的分发。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(handleMessage)) {
return [SurrogateViewController instanceMethodSignatureForSelector:@selector(newMethodOfHandleMessage)]
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if (selector == @selector(handleMessage)) {
[anInvocation setSelector:@selector(newMethodOfHandleMessage)];
SurrogateViewController *homeVC = [SurrogateViewController new];
[anInvocation invokeWithTarget:homeVC];
}
else {
[super forwardInvocation:anInvocation];
}
}
以上三种方法可三选一。若实现了多个,则根据生命周期以先执行的为准,后执行的将被忽略。
参考
1https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW1
2 https://www.jianshu.com/p/5127ce0628be
3 http://www.cocoachina.com/ios/20150604/12013.html