Objective-C 之所以能在运行时动态添加方法,是因为它在底层将“类”设计成了一个可变的数据结构,并且拥有一套基于名称查找而非地址偏移的消息分发系统。
这可以从以下三个层面来透彻理解:
1. 类的本质:一个动态的“字典”
在 C++ 或 Java(非反射部分)中,类的方法在编译后通常变成了一个固定的函数指针表(V-Table)。如果你想加个方法,必须改源代码重新编译。
但在 Objective-C 中,类对象(objc_class)内部维护的是一个方法列表(Method List) 。你可以把它想象成一个动态字典:
- Key(键): 是方法的名称(
SEL,选择子)。 - Value(值): 是代码的具体实现地址(
IMP,函数指针)。
既然是“字典”,Runtime 库(底层 C 函数库)就提供了类似 dictionary_add 的操作。class_addMethod 函数的本质就是向这个字典里插入了一个新的 Key-Value 对。
2. 消息机制的“解耦”
这是最核心的原因。在 Objective-C 中,调用方法 [obj method] 会被翻译成 objc_msgSend(obj, @selector(method))。
- 编译期:编译器只负责把
method这个名字变成一个唯一的字符串(SEL)。它不需要知道这个method对应的代码在哪,甚至不需要知道它存不存在。 - 运行时:只有当代码运行到这一行时,Runtime 才会拿着这个名字去类的“字典”里搜。
正是因为“找代码”这个动作发生在运行的那一刻,所以只要你在调用之前把方法塞进那个“字典”里,程序就能跑通。
3. 动态方法解析(自救机制)
Objective-C 甚至在语言层面专门为“动态添加方法”留了后门,即 动态方法解析(Dynamic Method Resolution) 。
当 objc_msgSend 在类的方法列表里找不到某个方法时,它不会立即崩溃,而是会触发一个特殊的钩子函数: +resolveInstanceMethod:(或 +resolveClassMethod:)。
在这个函数里,你可以临时写一段逻辑:
“噢,我发现你要调用
fly方法,但我现在没有。别急,我现在立刻用class_addMethod动态给你造一个!”
这就是 Core Data 框架(@dynamic 属性)以及很多**热修复(Hotfix)**框架能够生效的底层逻辑。
4. 具体操作:如何动态添加?
底层函数如下:
Objective-C
// cls: 给哪个类加
// name: 方法名 (SEL)
// imp: 函数指针 (C函数)
// types: 方法签名(描述参数和返回值)
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
这个动作会修改类对象中的 bits 数据段。由于类对象(Class Object)在内存中是唯一的且一直存在的,一旦添加成功,所有该类的实例都能立刻“学会”这个新技能。
总结
Objective-C 允许动态添加方法,是因为:
- 数据结构支持:类的方法列表是可写的链表/数组。
- 查找机制支持:查找行为推迟到了运行那一刻,给“临时加戏”留出了空间。
- 设计哲学支持:它继承了 Smalltalk “万物皆对象且可交互”的思想。