iOS的消息转发机制

先上一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的生命周期,我来依次介绍以下三种方法。

先来看一下流转图

iOS的消息转发机制_第1张图片
方法流转图
方法一:动态添加方法
// 对应实例方法
+ (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

你可能感兴趣的:(iOS的消息转发机制)