objc_msgSend(obj, @selector(foo)); 到底发生了什么?
objc_msgSend(obj, @selector(foo)); 到底发生了什么?
在 Objective-C 的世界里,有一句话几乎是底层原教旨主义:
Objective-C 是一门基于消息发送(Message Sending)的语言,而不是函数调用。
而这一切,都浓缩在一行看似普通、却极其核心的代码中:
objc_msgSend(obj, @selector(foo));
本文将从语法糖 → 运行时 → 完整调用链,一步一步拆解:
- 这行代码到底在“发什么”
- 消息是如何被找到并执行的
- 如果找不到方法,Runtime 又做了什么
一、从表面看:它等价于什么?
这行代码:
objc_msgSend(obj, @selector(foo));
在语义上 等价于:
[obj foo];
也就是说:
给对象 obj 发送一条名为 foo 的消息
[obj foo] 只是编译器提供的语法糖,真正执行的永远是 objc_msgSend。
二、谁是发送者?谁是接收者?
很多初学者会卡在这个问题上:
到底是谁“调用”了谁?
正确理解方式
-
接收者(receiver) :obj
-
消息(selector) :foo
-
发送动作的发起者:当前代码位置(不重要)
Objective-C 不关心调用栈的“谁” ,只关心:
👉 这条消息发给谁
所以永远用这句话来理解:
给 obj 发送 foo 消息
三、Runtime 真正发生的 6 个步骤(核心)
下面是你在 Xcode 里写下一行 [obj foo] 后,Runtime 在背后真实发生的完整流程。
步骤 1️⃣:取得接收者obj
id obj = ...;
objc_msgSend(obj, @selector(foo));
- obj 是一个对象指针
- 本质上指向一块内存
- 内存布局的第一个成员,就是 isa 指针
obj
├─ isa → Class
├─ ivar1
├─ ivar2
如果 obj == nil:
- 整个流程直接结束
- 返回 0 / nil
- 不会崩溃(OC 的著名特性)
步骤 2️⃣:通过isa找到 Class
Class cls = obj->isa;
-
isa 指向对象所属的类
-
这是 所有方法查找的起点
示例:
@interface Person : NSObject
- (void)foo;
@end
obj (Person 实例)
└─ isa → Person
步骤 3️⃣:在方法缓存(cache)中查找
Runtime 首先查 cache,而不是方法列表。
Class Person
├─ cache ← ① 先查这里
├─ methodList ← ② 再查这里
└─ superclass
-
cache 是一个哈希表:SEL → IMP
-
命中 cache = 极快(接近 C 函数调用)
如果在 cache 中找到了 foo:
IMP imp = cache[foo];
imp(obj, @selector(foo));
→ 流程结束****
步骤 4️⃣:在方法列表 & 父类中查找
如果 cache 未命中:
- 查 Person 的方法列表
- 找不到 → superclass
- 一直向上查,直到 NSObject
Person
↓
NSObject
如果在某个类中找到:
- 将 SEL → IMP 放入 cache(下次更快)
- 立即执行 IMP
步骤 5️⃣:动态方法解析(resolve)
如果 整个继承链都没找到 foo:
Runtime 会给你一次**“临时补救”的机会**。
+ (BOOL)resolveInstanceMethod:(SEL)sel;
示例:动态添加方法
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo)) {
class_addMethod(self, sel, (IMP)fooIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooIMP(id self, SEL _cmd) {
NSLog(@"动态添加的 foo 被调用了");
}
@end
如果返回 YES:
- Runtime 重新从步骤 3 开始查找
步骤 6️⃣:消息转发(Message Forwarding)
如果你没有动态添加方法,Runtime 进入 消息转发三连。
6.1 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return otherObj;
}
return [super forwardingTargetForSelector:aSelector];
}
等价于:
[otherObj foo];
6.2 完整转发(NSInvocation)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:otherObj];
}
6.3 最终失败 → 崩溃
如果以上都没处理:
-[Person foo]: unrecognized selector sent to instance
应用直接崩溃 💥
四、完整流程总览(记住这个顺序)
objc_msgSend
↓
isa
↓
cache
↓
method list
↓
superclass
↓
resolveInstanceMethod
↓
forwardingTargetForSelector
↓
forwardInvocation
↓
crash
五、为什么 objc_msgSend 这么重要?
-
KVC / KVO
-
方法交换(Method Swizzling)
-
AOP / Hook
-
崩溃防护
-
热修复(早期方案)
全都建立在它之上。
理解 objc_msgSend,
才算真正“入门” Objective-C Runtime。
六、结语
当你再看到这行代码时:
objc_msgSend(obj, @selector(foo));
请在脑中自动展开:
cache → superclass → resolve → forwarding → crash
那一刻,你已经不是在“写 OC”,而是在和 Runtime 对话。