小萝莉说Crash(二): Unrecognized selector xxx 之 ForwardInvocation
来源:互联网 发布:数据挖掘 招聘 银行 编辑:程序博客网 时间:2024/05/22 16:42
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. 有准备才能抓住机会 —— 实现动态加载方法
如果你有意识到此类崩溃问题,并期望可以在运行时有机会添加缺失的方法,那么你就可以通过实现NSObject
的resolveInstanceMethod:
方法,并利用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. 再次改过自新的机会 —— 快速消息转发
如果你没有采用动态加载方法处理此类问题,即不实现NSObject
的resolveInstanceMethod:
方法,你也可以实现NSObject
的 forwardingTargetForSelector:
方法,以声明一个新的类对象来处理这个消息。
如下:
- (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. 机不可失,失不再来 —— 标准消息转发
如果你只想规避此类问题,那你可以通过实现NSObject
的methodSignatureForSelector:
和forwardInvocation:
方法来进行消息的转发处理,以规避此类问题。方法methodSignatureForSelector:
返回一个任意一个非nil
的NSMethodSignature
对象,就可以进入到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的崩溃发生,通常的做法是创建NSObject
、UIViewController
等基础类的子类ForwardNSObject
、ForwardUIViewController
等,实现消息转发处理,项目声明的所有其他类都继承ForwardNSObject
、ForwardUIViewController
类即可。
三、小结
以上内容即是萝莉给大家分享的全部内容,绝对是规避Unrecognized selector sent to instance xxx崩溃问题的利器,而实际上,崩溃的发生和规避的方式都是由Objective-C的Runtime特性决定的。所以,我们在以后的开发过程,除了要熟悉CocoaTouch框架,也要学习一些Runtime的内容,它一定会给你带来惊喜。
虽然我们知道了规避Unrecognized selector sent to instance xxx崩溃问题的方法,但对于开发者来说,不能规避后就不去关注这个崩溃问题。所以,对于开发者的建议是:实现ForwardInvocation后,通过宏定义控制在发布版本生效,在开发阶段的还是要把此类问题暴露,并尽早做修复处理。
- 小萝莉说Crash(二): Unrecognized selector xxx 之 ForwardInvocation
- 【小萝莉说Crash】第二期:Unrecognized selector xxx 之 ForwardInvocation
- IOS crash之unrecognized selector
- 小萝莉说Crash(一):Unrecognized selector sent to instance xxxx
- 【小萝莉说Crash】第一期:Unrecognized selector sent to instance xxxx
- 小萝莉说Crash(一):Unrecognized selector sent to instance xxxx
- [NSCFType XXX]: unrecognized selector sent to instance 0x4d80b00'
- [UIView setShowsFPS:]: unrecognized selector sent to instance XXX
- [ XXX handleSegmentControl:]: unrecognized selector sent to instance 0x7fe2de49a8e0
- runtime之forwardInvocation
- CRASH: -[NSNull length]: unrecognized selector sent to instance错误的处理办法
- CRASH: -[NSNull length]: unrecognized selector sent to instance错误的处理办法
- CRASH: -[NSNull length]: unrecognized selector sent to instance错误的处理办法
- CRASH: -[NSNull length]: unrecognized selector sent to instance错误的处理办法
- iOS中的crash防护(一)unrecognized selector sent to instance
- iOS crash:[__NSCFNumber length]: unrecognized selector sent to instance 0xb000000000000053
- iOS开发之Debug之unrecognized selector sent to instancexxx
- forwardInvocation
- 练习3-1 重写binsearch函数,使得在循环内部只执行一次测试,比较两种版本函数的运行时间
- POJ 2187 Beauty Contest
- objective-C 编程全解-第08章 类NSObject和运行时系统 中 下
- SQLHelper
- tomcat https访问设置
- 小萝莉说Crash(二): Unrecognized selector xxx 之 ForwardInvocation
- 第15周实践项目项目2——洗牌
- curl 读取cookie并进行测试
- OKhttp使用笔记
- 页面跳转并传递数据
- Android Class加载机制(未完)
- 【网络】HTTP协议中的长连接和短连接(keep-alive状态)
- armitage无法开启解决方法
- C++中关于指针与数组的练习 2016.6.7