3-24.【OC】【Runtime】什么是“动态方法解析”?

3 阅读2分钟

“动态方法解析”(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 找不到方法时,内部会经历以下步骤:

  1. 触发回调:系统调用 resolveInstanceMethod:
  2. 动态注入:你在函数内部通过 class_addMethodSEL 关联到一个现成的 IMP(函数实现)上。
  3. 重新查找:如果你返回 YES,Runtime 会重新执行一遍方法查找流程。由于此时 class_addMethod 已经把方法塞进去了,这次查找会直接命中缓存,顺利执行。
  4. 失败降级:如果你返回 NO 或没做处理,系统会进入下一阶段:消息转发(Message Forwarding)

4. 为什么要用动态解析?(实际应用)

  • Core Data 的 @dynamic 属性:这是最著名的应用。Core Data 的属性在编译时不生成 getter/setter,而是在运行时通过动态方法解析,将其映射到数据库的存取逻辑上。
  • 按需加载:如果你的类有几百个方法,但某些方法极其罕见(比如某些插件功能),你可以不预先实现它们,而是等用户真正触发时,再去下载代码或动态注入实现,从而减小二进制包的体积。
  • 避免崩溃:作为一种兜底方案,可以在解析失败时统一指向一个“空操作”函数,防止 App 在遇到未知消息时闪退。

总结

动态方法解析就像是 Runtime 给开发者的一次**“临场加戏”**的机会。它让你能在方法被调用的那一刻,才真正去决定这个方法该做什么。