Skip to content

iOS 消息转发机制

当对象接收到无法解读的消息(unrecognized selector sent to instance 0x87),就会启动消息转发机制,由程序员指定处理方法。

消息转发两大阶段

  1. 动态方法解析

    检查当前接收者能否动态添加方法,处理这个unrecognized selector

  2. 完整的消息转发

    第一阶段执行完毕后,如接收者不能以动态新增方法处理这个unrecognized selector,接下来,会分两种情况:

    • 有备援接收者(replacement receiver)

      在运行期将消息转给备援接收者,由备援接收者完成消息的处理。

    • 无备援接收者(replacement receiver)

    • 启动一套“完整的消息转发机制”,将消息封装到 NSInvocation 对象中,交给接收者处理。

动态方法解析

如果没有找到需要执行的方法,会根据方法的类型,执行不同的处理方法。

处理无法调用的类方法

objc
+ (BOOL)resolveClassMethod:(SEL)sel;

处理无法调用的实例方法

objc
+ (BOOL)resolveInstanceMethod:(SEL)sel;

一般是提前写好相关的实现代码,通过 Runtime 在此处插入到类中。

e.g.

objc
/**
 没有找到 SEL 的实现时会执行下方的方法
 @param sel 当前对象调用并且找不到 IML 的 SEL
 @return 是否可以处理这个方法,并返回 yes
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 当返回 YES 时
// 在这里通过 Runtime 在将已经写好实现的代码插入到类中。
// ...
    return YES;
// 当返回 NO 时
// 会接着执行 forwordingTargetForSelector:方法
//    return NO;
}

消息转发

有备援接收者

在方法 - (id)forwardingTargetForSelector:(SEL)aSelector 中返回可以处理该消息的对象,交由该对象去处理这个消息。 如果此处返回 nil,则表示没有其他对象可以处理这个消息,然后通过完整的消息转发机制来处理。

objc
/**
 将当前对象不存在的 SEL 传给其他存在该 SEL 的对象
 @param aSelector 当前类中不存在的 SEL
 @return 存在该 SEL 的对象
 */
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 不传递给其他对象
// 将会执行 - (void)forwardInvocation:(NSInvocation *)anInvocation;
    return nil;

// 传递给一个其他对象,处理这个方法。
// 可以做相应的错误处理等
// 让 OtherClass 中相应的 SEL 去执行该方法
//    return [[OtherClass alloc] init];
}

无备援接收者

如果接收者不能处理消息,并且没有备援接收者,最终只能采取一个完整的消息转发来处理消息。

通过 NSInvocation 包装方法的目标、参数等,然后通过 - (void)forwardInvocation:(NSInvocation *)invocation 将消息指派给目标对象。

objc
- (void)forwardInvocation:(NSInvocation *)invocation
{
    SecondClass * forwardClass = [SecondClass new];
    SEL sel = invocation.selector;
    if ([forwardClass respondsToSelector:sel]) {
        [invocation invokeWithTarget:forwardClass];
    } else {
        [self doesNotRecognizeSelector:sel];
    }
}