小萝莉说Crash(二): Unrecognized selector xxx 之 ForwardInvocation

2015年不急不忙地到来,小萝莉为大家奉上新年礼包,祝大家新年快乐,希望开发GGMM们新一年的开发工作更加顺利、安心! ^_^

上篇的分享中,小萝莉给大家介绍了一个入门必现的应用崩溃问题 —— Unrecognized selector sent to instance xxx,通过分析其出现的主要场景,给大家提出了一些避免出现此类问题的建议。然而,古语有云:“斩草不除根,则必留后患”(感觉好邪恶的样子,嘿嘿嘿)。

今天,小萝莉就要给大家分享规避此类问题的终极利器 —— ForwardInvocation(消息重定向)

一、崩溃问题产生的过程

知识回顾 Objective-C的方法调用实际是一种消息传递,当向Objective-C对象发送一个消息时,Runtime如果在当前类及父类中找不到此selector对应的方法,在执行一个消息转发的流程后,最终产生一个崩溃,即前面提到的Unrecognized selector sent to instance xxx问题。(公众号回复“2001”,回顾“小萝莉说Crash(一):Unrecognized selector sent to instance xxx”)

实际上,应用出现Unrecognized selector sent to instance xxx问题是在一个消息传递转发流程执行完毕后,实在是找不到可以接收消息的对象时,才会抛出一个崩溃错误。(让我处理这消息,真心做不到啊=_=)

1. “臣妾”真的做不到 —— 消息转发流程

Objective-C的方法调用的消息传递过程按照如下流程执行:

消息转发过程的关键方法

  • 动态方法解析 向当前类发送resolveInstanceMethod:消息,检查是否动态向类添加了方法,如果返回YES,则系统认为方法已经被添加,则会重新发送消息。
  • 快速消息转发 检查当前类是否实现forwardingTargetForSelector:方法,若实现则调用,如果方法返回值为非nil或非self的对象,则向返回的对象重新发送消息。
  • 标准消息转发 Runtime发送methodSignatureForSelector:消息获取selector对应方法的签名,如果有方法签名返回,则根据方法签名创建描述消息的NSInvocation,向当前对象发送forwardInvocation:消息,如果没有方法签名返回,即返回值为nil,则向当前对象发送doesNotRecognizeSelector:消息,应用崩溃退出。

二、崩溃问题规避方法

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

1. 有准备才能抓住机会 —— 实现动态加载方法

如果你有意识到此类崩溃问题,并期望可以在运行时有机会添加缺失的方法,那么你就可以通过实现NSObjectresolveInstanceMethod:方法,并利用class_addMethod方法动态添加函数。如下:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@" resolve instance method: %@", 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. 再次改过自新的机会 —— 快速消息转发

如果你没有采用动态加载方法处理此类问题,即不实现NSObjectresolveInstanceMethod:方法,你也可以实现NSObjectforwardingTargetForSelector:方法,以声明一个新的类对象来处理这个消息。 如下:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwarding target for selector: %@", 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. 机不可失,失不再来 —— 标准消息转发

如果你只想规避此类问题,那你可以通过实现NSObjectmethodSignatureForSelector:forwardInvocation:方法来进行消息的转发处理,以规避此类问题。方法methodSignatureForSelector:返回一个任意一个非nilNSMethodSignature对象,就可以进入到forwardInvocation:方法,在这个方法里可以转发消息,也可以什么都不做。 如下:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"method signature for selector: %@", 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:方法,否则,应用仍然会崩溃。

上述三种措施都可以有效的避免Unrecognized selector sent to instance xxx的崩溃发生,通常的做法是创建NSObjectUIViewController等基础类的子类ForwardNSObjectForwardUIViewController等,实现消息转发处理,项目声明的所有其他类都继承ForwardNSObjectForwardUIViewController类即可。

三、小结

以上内容即是萝莉给大家分享的全部内容,绝对是规避Unrecognized selector sent to instance xxx崩溃问题的利器,而实际上,崩溃的发生和规避的方式都是由Objective-C的Runtime特性决定的。所以,我们在以后的开发过程,除了要熟悉CocoaTouch框架,也要学习一些Runtime的内容,它一定会给你带来惊喜。

虽然我们知道了规避Unrecognized selector sent to instance xxx崩溃问题的方法,但对于开发者来说,不能规避后就不去关注这个崩溃问题 所以,对于开发者的建议是:实现ForwardInvocation后,通过宏定义控制在发布版本生效,在开发阶段的还是要把此类问题暴露,并尽早做修复处理。

原文发布于微信公众号 - 腾讯Bugly(weixinBugly)

原文发表时间:2015-01-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏wOw的Android小站

[Objective-C]深入理解GCD

GCD(Grand Central Dispatch)是libdispatch的市场名称,而libdispatch作为Apple的一个库,为并发代码在多核硬件(...

16710
来自专栏Spring相关

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解

随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的...

85850
来自专栏比原链

剥开比原看代码17:比原是如何显示交易的详细信息的?

Gitee地址:https://gitee.com/BytomBlockchain/bytom

8810
来自专栏WindCoder

iOS集中和解耦网络:具有单例类的AFNetworking教程

当涉及iOS架构模式时,模型 - 视图 - 控制器(MVC)设计模式对于应用程序的代码库的长寿和可维护性是非常有用的。通过将它们解耦从而使类可以很容易地被重用或...

31710
来自专栏菩提树下的杨过

thrift中的超时(timeout)坑

最近在项目中采用thrift作为后台服务rpc框架,总体用下来性能还不错,跨语言特性使用起来也还行,但是也遇到了一些坑,其中之一就是超时问题(timeout),...

74090
来自专栏小鹏的专栏

tf API 研读5:Data IO

数据IO {Data IO (Python functions)} 一个TFRecords 文件为一个字符串序列。这种格式并非随机获取,它比较适合大规模的数...

19560
来自专栏高性能服务器开发

(六)关于网络编程的一些实用技巧和细节

这些年,接触了形形色色的项目,写了不少网络编程的代码,从windows到linux,跌进了不少坑,由于网络编程涉及很多细节和技巧,一直想写篇文章来总结下这方面的...

41070
来自专栏小曾

.Net Web开发技术栈

有很多朋友有的因为兴趣,有的因为生计而走向了.Net中,有很多朋友想学,但是又不知道怎么学,学什么,怎么系统的学,为此我以我微薄之力总结归纳写了一篇.Net w...

46730
来自专栏.net core新时代

数据字典生成工具之旅系列文章导航

数据字典生成工具之旅系列文章导航 宣传语 数据字典生成工具、数据字典文档生成工具、NPOI入门、NPOI下载、NPOI中文教程、NPOI实例、DocX组件操作W...

26290
来自专栏程序员互动联盟

【专业技术】浏览器内核缓冲机制剖析

编者按:缓存能极大提高用户体验,这一点众所周知,下面我们一起来探究Webkit里面的memorycache。 WebKit将资源分为main resouce和s...

28450

扫码关注云+社区

领取腾讯云代金券