1-16.【OC】【基础】objc_msgSend 的完整调用流程是怎样的?

4 阅读3分钟

objc_msgSend 是 Objective-C 的灵魂。为了保证极高的执行效率,它的核心逻辑是用汇编语言编写的。其调用流程可以分为三个阶段:消息发送(快速与慢速路径)动态方法解析消息转发

我们可以把这个过程想象成一个“逐级上报的求助系统”:


第一阶段:消息发送 (Messaging) - 寻找 IMP

当调用 [receiver selector] 时,底层触发 objc_msgSend(receiver, selector)

  1. 检查过滤器 (Entry Check)

    • 检查 receiver 是否为 nil。如果为 nil,直接返回(这就是为什么向 nil 发消息不崩溃)。
    • 如果是 Tagged Pointer,执行专门的快速处理路径。
  2. 快速路径 (Fast Path) - 查找 Cache

    • 通过 receiverisa 指针找到 Class对象
    • 在 Class 的 cache(哈希表)中寻找匹配的 SEL
    • 命中:直接跳转到对应的 IMP(函数地址)执行。
    • 未命中:进入慢速路径。
  3. 慢速路径 (Slow Path) - 递归查找方法表

    • 本类查找:遍历当前类的 methodList。如果找到,填充到 cache 并执行。
    • 继承链查找:通过 superclass 指针逐级往上找(父类 \to 根类)。每上一级都会先查该类的 cache,再查其 methodList
    • 如果查到 NSObject 还是没有,就进入第二阶段。

第二阶段:动态方法解析 (Dynamic Method Resolution) - 临时自救

如果在继承链中死活找不到,Runtime 会最后给当前类一次“补救”的机会:

  • 调用 +resolveInstanceMethod: (或类方法的 +resolveClassMethod:)。
  • 开发者操作:你可以在这里判断 selector,然后使用 class_addMethod 动态地把一个函数实现塞进去。
  • 如果返回 YES,Runtime 会重新启动“第一阶段”的消息发送流程。

第三阶段:消息转发 (Message Forwarding) - 甩锅他人

如果动态解析也没搞定(返回 NO),说明本类确实处理不了,进入正式的“甩锅”环节:

1. 快速转发 (Forwarding Target)

  • 调用 -(id)forwardingTargetForSelector:(SEL)aSelector
  • 开发者操作:直接返回一个能处理这个消息的其他对象(备援接收者)。
  • 如果返回非空对象,Runtime 会把消息转发给那个对象,流程结束。

2. 标准转发 (Normal Forwarding)

如果快速转发也返回 nil,这是最后的“大招”:

  • 获取签名:调用 -(NSMethodSignature *)methodSignatureForSelector:。如果返回 nil,直接报 Unrecognized Selector 崩溃。
  • 封装调用:如果返回了签名,Runtime 会把消息的所有细节(参数、目标、选择子)封装成一个 NSInvocation 对象。
  • 最终投递:调用 -(void)forwardInvocation:(NSInvocation *)anInvocation
  • 开发者操作:这是权限最高的一步。你可以修改参数、修改目标,或者干脆什么都不做(静默掉错误)。

总结全流程图

  1. objc_msgSend (汇编实现)

    • \to 检查 nil
    • \to 查当前类 cache (命中则 jump)
  2. lookUpImpOrForward (C实现)

    • \to 遍历当前类 methodList
    • \to 递归找 superclasscache & methodList
  3. 动态方法解析

    • \to resolveInstanceMethod
  4. 消息转发

    • \to forwardingTargetForSelector (找人代跑)
    • \to methodSignatureForSelector + forwardInvocation (最后的挣扎)
  5. 报错

    • \to doesNotRecognizeSelector: (抛出异常)

💡 深度思考:为什么这么设计?

这种设计完美体现了 C 的性能Smalltalk 的灵活性

  • 快速路径:全汇编,缓存哈希查找,性能接近 C 函数调用。
  • 慢速路径/转发:全动态,允许在运行时通过“甩锅”和“自救”实现诸如 AOP(面向切面编程)热修复Proxy 代理 等高级工程模式。