OC底层原理(10)消息转发

103 阅读2分钟

一. forwardingTargetForSelector 快速转发

首先定义 YJPerson 类和 YJProxy 类,然后在 main 函数中调用say1方法。YJPerson 没有实现 say1 方法,YJProxy 类实现say1方法

@implementation LWPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"--%s", __func__);
    if (aSelector == @selector(say1)) {
        return [[YJProxy alloc] init];
    }  
    return [super forwardingTargetForSelector:aSelector];
}
@end
@implementation YJProxy
- (void)say1 {
    NSLog(@"--%s", __func__);
}
@end

调用:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YJPerson *person = [[YJPerson alloc] init];
        [person say1];
    }
    return 0;
}

输出:

Xnip2022-07-13_09-40-55.png

打印结果显示:YJProxy 类和 YJPerson 类没有任何关系,但是指定给YJProxy 类,仍然最后可以查询到,并且没有崩溃消息,其实消息在查询过程中先去跟它 关系近的类中去查找,最后没找到。于是系统把这个权限丢给开发者,问你这事儿能不能你交给其它谁帮你整

二. forwardInvocation 慢速转发

慢速转发 forwardInvocation 也是消息查找的最后一个流程

Xnip2022-07-13_10-02-27.png

大概意思就是说:除了实现 forwardInvocation:外, 还必须实现methodSignatureForSelector:。 转发消息的机制通过从methodSignatureForSelector: 返回的信息来创建要转发的 NSInvocation 对象。使用forwardInvocation,必须先给它生成个方法签名

代码测试:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"--%s", __func__);

    NSMethodSignature *sign = [super methodSignatureForSelector:aSelector];
    NSLog(@"sign = %@", sign);
    return sign;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"--%s", __func__);
    NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

输出结果:

Xnip2022-07-13_10-13-30.png

[super methodSignatureForSelector:aSelector]; 返回了 nil,结果直接奔溃了,正应了文档中说的 methodSignatureForSelector: 必须返回后一个 NSMethodSignature 对象。修改代码再来:

@interface YJPerson ()
@property (nonatomic, strong) YJProxy *helper; 
@end

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"--%s", __func__);
    if (aSelector == @selector(say1)) {
        return [self.helper methodSignatureForSelector:@selector(say1)];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"--%s", __func__);
    NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

输出:

Xnip2022-07-13_10-54-21.png

修改 forwardInvocation 实现:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"--%s", __func__);
    NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    
    if ([self.helper respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.helper];
    }
}

输出:

Xnip2022-07-13_10-58-29.png

这次指定了 target, 将原来 YJPerson对象 改为了 YJProxy;并成功调用了 -[YJProxy say1]。感觉这和快速转发 forwardingTargetForSelector 差不多,都是重新指定个对象,去实现。是这样么?再次修改 forwardInvocation 实现:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"--%s", __func__);
    NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    
    nInvocation.selector = @selector(say2);
    anInvocation.target = self.helper;
    [anInvocation invoke];
}

输出:

Xnip2022-07-13_11-05-44.png

nInvocation 不仅能指定 target 还能指定 selector, 这就灵活多了,

总结:

  • 快速转发:通过forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程

  • 慢速转发:通过methodSignatureForSelectorforwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束-奔溃,如果有返回值 forwardInvocation 会执行,且可处理可不处理,都不会崩溃

消息转发流程图

aaa.png