“动态方法解析”(Dynamic Method Resolution)是 Objective-C 消息转发机制的第一道防线。
当对象收到一条它无法识别的消息(即在 cache 和方法列表中都没找到对应的 IMP)时,Runtime 并不会立刻让程序崩溃,而是给当前的类一个机会:在运行时动态地为一个 Selector 提供实现。
1. 动态方法解析的位置
在整个消息处理的生命周期中,它处于最前端。如果这一步成功了,后续昂贵的消息转发逻辑(如 forwardingTargetForSelector:)就不会被触发。
2. 如何实现?
根据调用的是实例方法还是类方法,你需要重写类中的特定方法:
场景 A:动态解析实例方法
重写 +resolveInstanceMethod:。
Objective-C
#import <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@">> 动态解析的方法被调用了!对象是:%@, 方法名是:%@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(goShopping)) {
// 为该 Selector 动态绑定一个 C 函数实现
// "v@:" 表示:返回 void,参数是 id(self) 和 SEL(_cmd)
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES; // 告诉系统:我已经搞定了,请重新查找并调用
}
return [super resolveInstanceMethod:sel];
}
场景 B:动态解析类方法
重写 +resolveClassMethod:。
注意:类方法存储在 元类(Meta Class) 中,所以
class_addMethod的第一个参数应该是object_getClass(self)。
3. 底层运行流程
当 objc_msgSend 找不到方法时,内部会经历以下步骤:
- 触发回调:系统调用
resolveInstanceMethod:。 - 动态注入:你在函数内部通过
class_addMethod将SEL关联到一个现成的IMP(函数实现)上。 - 重新查找:如果你返回
YES,Runtime 会重新执行一遍方法查找流程。由于此时class_addMethod已经把方法塞进去了,这次查找会直接命中缓存,顺利执行。 - 失败降级:如果你返回
NO或没做处理,系统会进入下一阶段:消息转发(Message Forwarding) 。
4. 为什么要用动态解析?(实际应用)
- Core Data 的
@dynamic属性:这是最著名的应用。Core Data 的属性在编译时不生成 getter/setter,而是在运行时通过动态方法解析,将其映射到数据库的存取逻辑上。 - 按需加载:如果你的类有几百个方法,但某些方法极其罕见(比如某些插件功能),你可以不预先实现它们,而是等用户真正触发时,再去下载代码或动态注入实现,从而减小二进制包的体积。
- 避免崩溃:作为一种兜底方案,可以在解析失败时统一指向一个“空操作”函数,防止 App 在遇到未知消息时闪退。
总结
动态方法解析就像是 Runtime 给开发者的一次**“临场加戏”**的机会。它让你能在方法被调用的那一刻,才真正去决定这个方法该做什么。