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)。
- Core Data 的
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 异常,程序崩溃。
流程总结图
[对象收到消息 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)];
#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等高级架构设计的核心技术。