3-28.【OC】【Runtime】如果 forwardInvocation: 没有处理消息,会发生什么?

3 阅读2分钟

如果 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 的“大招”——防崩溃组件就是这么干的:

  1. 第一步:为 NSObject 编写一个分类(Category)。
  2. 第二步:重写 methodSignatureForSelector:。如果发现方法不存在,不要返回 nil,而是返回一个通用的“空方法”签名(比如 v@:)。
  3. 第三步:重写 forwardInvocation:,在里面写一行日志(上传错误堆栈到服务器),但不做任何处理。

这样,原本会导致 App 闪退的“方法找不着”问题,就被静默转化成了一条后台日志,App 依然能保命运行。


总结:转发失败的完整路径

  1. forwardInvocation: 被调用。
  2. 你处理了 \rightarrow 流程正常结束。
  3. 你没处理(空实现) \rightarrow 流程正常结束,但调用方拿到空返回值。
  4. 你没实现(调 super) \rightarrow 调用 doesNotRecognizeSelector: \rightarrow 抛出异常并 Crash