基于本文回答

播面 播面

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

Objective-C 的消息转发 (Message Forwarding)?

知识点图片

Objective-C 是一门动态语言,方法的调用在编译期并不决定调用哪个函数,而是通过 Runtime(运行时) 将消息发送给对象(objc_msgSend)。

当向一个对象发送它无法响应的消息(即对象的方法列表中找不到对应的 Selector)时,程序并不会立即崩溃,而是会启动 消息转发(Message Forwarding) 机制。

这个机制给了开发者三次“自救”的机会。如果这三次机会都浪费了,程序才会抛出 unrecognized selector sent to instance 异常并崩溃。

以下是消息转发的完整流程图解与详细步骤:


消息转发的三大阶段

1. 动态方法解析 (Dynamic Method Resolution)

这是第一道防线。Runtime 会通知类:“你没有这个方法,要不要现在动态添加一个?”

  • 调用方法:
    • 实例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
    • 类方法:+ (BOOL)resolveClassMethod:(SEL)sel
  • 如何处理:
    在这个方法中,你可以利用 Runtime 的 class_addMethod 函数动态地向类中添加一个方法实现(IMP)。如果添加成功并返回 YES,Runtime 会重新尝试发送消息,流程结束。
  • 典型应用场景:
    • Core Data 的 @dynamic 属性(自动生成 getter/setter)。

2. 快速转发 (Fast Forwarding)

如果第一阶段返回 NO(或者未实现),Runtime 会问:“既然你自己处理不了,有没有别的对象(备胎)能帮忙处理?”

  • 调用方法:
    • - (id)forwardingTargetForSelector:(SEL)aSelector
  • 如何处理:
    返回一个非 nil 且非 self 的对象。Runtime 会将消息直接发送给这个返回的对象。
  • 特点:
    • 效率高:这一步只是简单的转移目标,不会创建昂贵的 NSInvocation 对象。
    • 局限:只能转发给一个对象,无法修改消息参数。
  • 典型应用场景:
    • 模拟多重继承(将消息转发给组合在内部的其他对象)。

3. 完整转发 (Normal/Full Forwarding)

如果第二阶段返回 nil(即没有备胎),Runtime 会启动最后且最慢的机制。它会将消息的所有细节(Target、Selector、参数)封装成一个 NSInvocation 对象。

这一步分为两个小步骤:

  • 步骤 3.1:方法签名

    • 调用方法: - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    • 作用: 你必须返回一个正确的方法签名(包含返回值类型、参数类型等),Runtime 才能利用它创建 NSInvocation 对象。如果这里返回 nil,流程直接终止,程序崩溃。
  • 步骤 3.2:转发调用

    • 调用方法: - (void)forwardInvocation:(NSInvocation *)anInvocation
    • 作用: 在这里你可以随心所欲地处理这个消息:
      • 修改调用目标([anInvocation invokeWithTarget:otherObject])。
      • 修改参数。
      • 甚至不调用任何方法,只是记录日志然后吞掉消息。
  • 典型应用场景:

    • NSProxy 的实现。
    • AOP(面向切面编程),如埋点统计、日志记录。
    • 将同一个消息转发给多个对象(广播)。

最终结局:崩溃

如果 forwardInvocation: 也没有处理(或者调用了 super 的实现),最终会调用 doesNotRecognizeSelector:,抛出 NSInvalidArgumentException 异常,程序崩溃。


流程总结图

plaintext
[对象收到消息 msg]
       |
       V
[查找方法列表/缓存] --(找到)--> [调用 IMP]
       |
    (找不到)
       |
       V
1. [resolveInstanceMethod:] --(YES/添加方法)--> [重新发送消息]
       |
     (NO)
       |
       V
2. [forwardingTargetForSelector:] --(返回非nil对象)--> [转发给该对象]
       |
     (nil)
       |
       V
3. [methodSignatureForSelector:] --(返回 nil)--> [崩溃: unrecognized selector]
       |
  (返回签名)
       |
       V
   [forwardInvocation:] --(处理调用)--> [流程结束]
       |
    (未处理)
       |
       V
[doesNotRecognizeSelector:] --> [崩溃]

代码示例

假设我们调用了一个不存在的方法 [self performSelector:@selector(missMethod)];

plaintext
#import "MyObject.h"
#import <objc/runtime.h>

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"1. 动态添加的方法被调用了");
}

@implementation MyObject

// --- 第一阶段:动态解析 ---
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(missMethod)) {
        // 如果想在这里解决,取消注释下面代码
        // class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        // return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// --- 第二阶段:快速转发 ---
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(missMethod)) {
        // 如果有一个 Helper 对象能处理,返回它
        // return [Helper new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// --- 第三阶段:完整转发 ---
// 3.1 获取签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(missMethod)) {
        // 返回 void xxx(void) 的签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// 3.2 处理调用
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 可以在这里改变目标,或者修改参数
    // Helper *helper = [Helper new];
    // if ([helper respondsToSelector:[anInvocation selector]]) {
    //     [anInvocation invokeWithTarget:helper];
    // } else {
        NSLog(@"3. 完整转发:虽然没有处理,但我把 Crash 拦截下来了");
    // }
}

@end

总结

Objective-C 的消息转发机制体现了该语言极大的灵活性。它不仅是处理错误的兜底方案,更是实现多重继承装饰器模式代理模式(NSProxy)以及AOP等高级架构设计的核心技术。

00:00
00:00