美文网首页IOS知识积累IOSiOS程序猿
iOS消息转发之 - "臣妾做不到"

iOS消息转发之 - "臣妾做不到"

作者: nenhall | 来源:发表于2017-08-07 11:47 被阅读210次

    iOS消息转发之 - "臣妾做不到"

    一、崩溃问题产生的过程:

    Objective-C的方法调用实际是一种消息传递,当向objective-c对象发送一个消息时,Runtime如果在当前类及父类中找不到此selector对应的方法,在执行一个消息转发的流程后,最终产生一个崩溃,出现Unrecognized selector sent to instance xxx问题,实在是找不到可以接收消息的对象时,才会抛出一个崩溃错误(让我处理这消息,真心做不到啊)。
    如下图:

    来自一张网络图

    消息转发过程的关键方法:

    1. 动态方法解析
      向当前类发送resolveInstanceMethod:消息,检查是否动态向类添加了方 法,如果返回YES,则系统认为方法已经被添加,则会重新发送消息。

    2. 快速消息转发
      检查当前类是否实现forwardingTargetForSelector:方法,若实现则调 用,如果方法返回值为非nil或非self的对象,则向返回的对象重新发送消息。

    3. 标准消息转发
      Runtime发送methodSignatureForSelector:消息获取selector对应方法的签名,如果有方法签名返回,则根据方法签名创建描述消息的NSInvocation,向当前对象发送forwardInvocation:消息,如果没有方法签名返回,即返回值为nil,则向当前对象发送doesNotRecognizeSelector:消息,应用崩溃退出

    二、崩溃问题规避方法

    当向某个对象发送消息,Runtime在当前类和父类中都找不到对应方法实现时,应用并不会立即崩溃退出,而是先执行一个完整的消息转发流程才会结束。这也就给了我们去修正问题的机会。

    1). 有准备才能抓住机会 —— 实现动态加载方法
    如果你有意识到此类崩溃问题,并期望可以在运行时有机会添加缺失的方法,那么你就可以通过实现NSObject的resolveInstanceMethod:方法,并利用class_addMethod方法动态添加函数。如下:

    + (BOOL)resolveInstanceMethod:(SEL)sel {  
        NSLog(@"%s >>>> %@", __func__, NSStringFromSelector(sel));  
      
        BOOL resolved = [super resolveInstanceMethod:sel];  
      
        if (!resolved) {  
            // 动态添加一个方法_dynamic_method_imp_处理消息  
            class_addMethod([self class], sel, (IMP)_dynamic_method_imp_, "v@:");  
      
            return YES; // 返回YES,表示消息转发成功,不会发生崩溃  
        }  
        return resolved;  
    } 
    

    2). 再次改过自新的机会 —— 快速消息转发
    如果你没有采用动态加载方法处理此类问题,即不实现NSObject的resolveInstanceMethod:方法,你也可以实现NSObject的 forwardingTargetForSelector:方法,以声明一个新的类对象来处理这个消息。如下:

    - (id)forwardingTargetForSelector:(SEL)aSelector {  
        NSLog(@"%s >>> %@",__func__, NSStringFromSelector(aSelector));  
      
        id cls = [super forwardingTargetForSelector:aSelector];  
      
        if (cls == nil) {  
            // 使用代理类处理消息(自定义的一个类)  
            ForwardProxy *p = [[ForwardProxy alloc] init];  
      
            if ([p respondsToSelector:aSelector]) {  
                return p; // 返回非nil,非self的对象,表示消息转发成功,不会发生崩溃  
            }  
        }  
      
        return cls;  
    }
    

    3). 机不可失,失不再来 —— 标准消息转发
    如果你只想规避此类问题,那你可以通过实现NSObject的methodSignatureForSelector:和forwardInvocation:方法来进行消息的转发处理,以规避此类问题。方法methodSignatureForSelector:返回一个任意一个非nil的NSMethodSignature对象,就可以进入到forwardInvocation:方法,在这个方法里可以转发消息,也可以什么都不做。
    如下:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {  
        NSLog(@"%s >>> %@", __func__, NSStringFromSelector(aSelector));  
      
        NSMethodSignature *ms = [super methodSignatureForSelector:aSelector];  
      
        if (ms == nil) {  
            // 创建一个非nil的方法签名,否则,不会进入forwardInvocation:方法进行消息转发  
            ms = [ForwardProxy instanceMethodSignatureForSelector:@selector(missMethod)];  
        }  
      
        return ms;  
    }  
      
    - (void)forwardInvocation:(NSInvocation *)anInvocation {  
        NSLog(@"forward invocation: %@", anInvocation);  
      
        if (anInvocation) {  
            // 处理转发的消息,进入此方法,就不会产生崩溃  
            [self missTarget:[anInvocation target] withSelector:[anInvocation selector]];  
        }  
    } 
    

    注意:实现forwardInvocation:方法时,不用调用super forwardInvocation:方法,否则,应用仍然会崩溃。

    本文的部份内容摘自:腾讯Bugly特邀文章

    相关文章

      网友评论

      本文标题:iOS消息转发之 - "臣妾做不到"

      本文链接:https://www.haomeiwen.com/subject/fiwolxtx.html