阅读视图

发现新文章,点击刷新页面。

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 未命中:

  1. 查 Person 的方法列表
  2. 找不到 → superclass
  3. 一直向上查,直到 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 对话

❌