如果 forwardInvocation: 依然没有处理消息(即在该方法内部既没有改变 target 执行,也没有手动调用 invoke,甚至干脆是个空实现),那么消息转发流程将走到终点。
接下来会发生两件事:
1. 触发 doesNotRecognizeSelector:
这是 NSObject 默认抛出异常的地方。当 Runtime 发现没有任何防线拦截住这条消息时,它会调用:
Objective-C
- (void)doesNotRecognizeSelector:(SEL)aSelector;
2. 程序崩溃并抛出异常
在默认情况下,doesNotRecognizeSelector: 内部会抛出一个 NSInvalidArgumentException 异常。
你在 Xcode 控制台中会看到那行最熟悉的错误日志:
"unrecognized selector sent to instance 0x..."
深入细节:空实现算“处理”了吗?
这是一个有趣的边界问题:如果在 forwardInvocation: 里写个空代码,程序还会崩吗?
答案是:不会崩。
虽然你什么都没做,但只要你重写了 forwardInvocation: 且程序运行到了这里,Runtime 就认为你已经“接手”了这档子事。
- 返回值处理:如果该方法原本有返回值,由于你没有处理
NSInvocation,调用方拿到的将是一个未定义的垃圾值(通常是 0、nil或执行时的栈残留数据)。这可能会导致后续逻辑出现不可预知的 Bug。
实际应用:全局“防崩溃”黑科技
利用这个特性,很多 App 的“大招”——防崩溃组件就是这么干的:
- 第一步:为
NSObject编写一个分类(Category)。 - 第二步:重写
methodSignatureForSelector:。如果发现方法不存在,不要返回nil,而是返回一个通用的“空方法”签名(比如v@:)。 - 第三步:重写
forwardInvocation:,在里面写一行日志(上传错误堆栈到服务器),但不做任何处理。
这样,原本会导致 App 闪退的“方法找不着”问题,就被静默转化成了一条后台日志,App 依然能保命运行。
总结:转发失败的完整路径
forwardInvocation:被调用。- 你处理了 流程正常结束。
- 你没处理(空实现) 流程正常结束,但调用方拿到空返回值。
- 你没实现(调 super) 调用
doesNotRecognizeSelector:抛出异常并 Crash。