objc_msgSend 是 Objective-C 的灵魂。为了保证极高的执行效率,它的核心逻辑是用汇编语言编写的。其调用流程可以分为三个阶段:消息发送(快速与慢速路径) 、动态方法解析、消息转发。
我们可以把这个过程想象成一个“逐级上报的求助系统”:
第一阶段:消息发送 (Messaging) - 寻找 IMP
当调用 [receiver selector] 时,底层触发 objc_msgSend(receiver, selector):
-
检查过滤器 (Entry Check) :
- 检查
receiver是否为nil。如果为nil,直接返回(这就是为什么向nil发消息不崩溃)。 - 如果是 Tagged Pointer,执行专门的快速处理路径。
- 检查
-
快速路径 (Fast Path) - 查找 Cache:
- 通过
receiver的isa指针找到 Class对象。 - 在 Class 的
cache(哈希表)中寻找匹配的SEL。 - 命中:直接跳转到对应的
IMP(函数地址)执行。 - 未命中:进入慢速路径。
- 通过
-
慢速路径 (Slow Path) - 递归查找方法表:
- 本类查找:遍历当前类的
methodList。如果找到,填充到cache并执行。 - 继承链查找:通过
superclass指针逐级往上找(父类 根类)。每上一级都会先查该类的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。 - 开发者操作:这是权限最高的一步。你可以修改参数、修改目标,或者干脆什么都不做(静默掉错误)。
总结全流程图
-
objc_msgSend(汇编实现)- 检查
nil - 查当前类
cache(命中则jump)
- 检查
-
lookUpImpOrForward(C实现)- 遍历当前类
methodList - 递归找
superclass的cache&methodList
- 遍历当前类
-
动态方法解析
-
resolveInstanceMethod
-
-
消息转发
-
forwardingTargetForSelector(找人代跑) -
methodSignatureForSelector+forwardInvocation(最后的挣扎)
-
-
报错
-
doesNotRecognizeSelector:(抛出异常)
-
💡 深度思考:为什么这么设计?
这种设计完美体现了 C 的性能 与 Smalltalk 的灵活性:
- 快速路径:全汇编,缓存哈希查找,性能接近 C 函数调用。
- 慢速路径/转发:全动态,允许在运行时通过“甩锅”和“自救”实现诸如 AOP(面向切面编程) 、热修复 和 Proxy 代理 等高级工程模式。