基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Objective-C 的 消息发送机制 (objc_msgSend 流程)

知识点图片

Objective-C 是一门动态语言,其核心机制就是消息发送 (Messaging)。在 C 语言中,函数调用在编译期就决定了跳转地址;而在 Objective-C 中,[obj foo] 并不会立即执行 foo 方法的代码,而是向 obj 发送了一条名为 foo 的消息。

这个过程的核心函数就是 objc_msgSend。整个流程大致可以分为三大阶段:

  1. 消息发送阶段 (Messaging / Lookup):查找方法的实现 (IMP)。
  2. 动态方法决议阶段 (Dynamic Method Resolution):如果找不到,给个机会动态添加方法。
  3. 消息转发阶段 (Message Forwarding):如果还没添加,将消息转发给其他对象处理。

0. 编译阶段

编译器会将你的代码转换成 C 语言函数调用:

plaintext
// 源代码
[receiver message:arg];

// 编译器转换后
objc_msgSend(receiver, @selector(message:), arg);

objc_msgSend 的原型为:
id objc_msgSend(id self, SEL op, ...)


1. 消息发送阶段 (Messaging)

这是最频繁发生的阶段,为了性能,苹果在 Runtime 中使用了大量的汇编代码来优化这一步。

1.1 判断 receiver 是否为 nil

  • 如果 receivernilobjc_msgSend 会直接返回(根据返回值类型返回 0、nil 或 void),不会进行后续查找。这就是为什么给 nil 发送消息不会崩溃的原因。

1.2 查找 Cache (快速路径 - Fast Path)

  • Runtime 会根据 receiverisa 指针找到它所属的 Class
  • 每个 Class 都有一个 方法缓存 (cache_t),用于存储最近调用过的方法。
  • 系统使用 SEL (方法编号) 通过哈希算法计算出在缓存 Hash Table 中的索引。
  • 命中:如果能在缓存中找到对应的 Bucket (包含 key 和 IMP),则直接跳转到 IMP 执行。这是最快的情况。
  • 未命中:如果缓存中没有,则进入慢速查找路径。

1.3 查找方法列表 (慢速路径 - Slow Path)

  • 这一步通常进入 C++ 函数 lookUpImpOrForward
  • 当前类查找:在当前 Class 的 method_list (方法列表) 中查找。
    • 如果是已排序列表,使用二分查找。
    • 如果是未排序列表,使用线性遍历。
    • 如果找到:填充到 Cache 中,并跳转执行 IMP。
  • 父类查找:如果当前类没找到,则通过 superclass 指针找到父类。
    • 先查父类的 Cache。
    • 再查父类的 method_list
    • 如果找到:填充到 Receiver 的 Class 的 Cache 中(注意不是父类的 Cache),并执行。
  • 循环向上:一直重复上述步骤,直到根类 (NSObject)。
  • 仍未找到:如果 NSObject 中也找不到,进入动态方法决议阶段。

2. 动态方法决议阶段 (Dynamic Method Resolution)

如果方法查找失败,Runtime 会给类一个机会,在运行时动态添加方法实现。

  • 实例方法:调用 + (BOOL)resolveInstanceMethod:(SEL)sel
  • 类方法:调用 + (BOOL)resolveClassMethod:(SEL)sel

操作流程:

  1. 开发者重写上述方法。
  2. 利用 class_addMethod 动态添加方法的实现 (IMP)。
  3. 返回 YES (实际上 Runtime 只要看你有没有添加方法,返回值在现代 Runtime 中不是决定性因素,但规范通常返回 YES)。
  4. 系统会重新触发一次消息发送流程 (回到第 1 阶段)。如果这次找到了(因为你刚才添加了),就正常执行;如果还没找到,进入下一阶段。
plaintext
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo)) {
        class_addMethod(self, sel, (IMP)fooMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

3. 消息转发阶段 (Message Forwarding)

如果动态决议没有解决问题(或者开发者没处理),系统会进入消息转发机制。这分为两步:快速转发和标准转发。

3.1 快速转发 (Fast Forwarding)

  • 调用方法:- (id)forwardingTargetForSelector:(SEL)aSelector
  • 目的:找一个“替身”对象来接收这个消息。
  • 逻辑:如果返回一个非 nil 且非 self 的对象,Runtime 会将消息重新发送给这个返回的对象。
  • 特点:效率高,因为不需要创建 NSInvocation 对象。
plaintext
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return otherObject; // 让 otherObject 去处理 foo
    }
    return [super forwardingTargetForSelector:aSelector];
}

3.2 标准转发 (Normal Forwarding)

如果快速转发返回 nil,则进入最后且开销最大的完整转发过程。

  1. 方法签名:调用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    • 你需要返回一个方法签名对象,描述方法的返回值和参数类型。
    • 如果返回 nil,则直接崩溃,抛出 unrecognized selector sent to instance
  2. 消息调用:如果签名不为空,Runtime 会创建一个 NSInvocation 对象(封装了 target, selector, arguments),然后调用 - (void)forwardInvocation:(NSInvocation *)anInvocation

    • 在这里,你可以随心所欲地处理消息:修改目标、修改参数、甚至什么都不做(吞掉消息)。
plaintext
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 可以指定其他对象处理
    [anInvocation invokeWithTarget:someOtherObject];
}

4. 最终结果:崩溃

如果 forwardInvocation: 也没有处理(或者 methodSignatureForSelector: 返回 nil),最终会调用 doesNotRecognizeSelector:,抛出经典的异常:

unrecognized selector sent to instance 0x...


总结图解

plaintext
[obj foo]
   |
   V
1. 判断 obj 是否为 nil? -> YES -> return (0/nil)
   | NO
   V
2. 查找 obj->isa (Class) 的 Cache? -> YES -> 执行 IMP
   | NO
   V
3. 查找 Class 的 method_list? -> YES -> 填充 Cache -> 执行 IMP
   | NO
   V
4. 递归查找 SuperClass (Cache & List)? -> YES -> 填充 Cache -> 执行 IMP
   | NO (直到 NSObject 也没找到)
   V
5. 动态方法决议 (+resolveInstanceMethod:)? -> 添加了方法 -> 重新开始查找
   | NO
   V
6. 快速转发 (-forwardingTargetForSelector:)? -> 返回新对象 -> objc_msgSend(新对象, foo)
   | NO (返回 nil)
   V
7. 方法签名 (-methodSignatureForSelector:)? -> 返回 nil -> Crash
   | YES (返回签名)
   V
8. 标准转发 (-forwardInvocation:)? -> 开发者手动处理
   | 不处理
   V
9. Crash (doesNotRecognizeSelector:)
00:00
00:00