iOS底层-消息转发

1,689 阅读5分钟

前言

在上一篇动态方法决议中,我们讲到消息慢速查找没找到会进入动态决议。如果动态决议没有处理,通过日志发现了新的东西forwardingTargetForSelectormethodSignatureForSelector,也就是消息查找的最后流程 消息转发

消息转发

  • 消息转发分为快速转发慢速转发,先来看看快速转发。

快速转发

快速转发也就是forwardingTargetForSelector,这个方法有什么作用呢,先来在objc-818.2源码中搜索,发现只提到了方法,没有找到具体使用。再来找下方法文档Organizer -> cmd + shift + 0,然后搜索forwardingTargetForSelector

forwardingTargetForSelector

截屏2021-07-08 17.17.55.png

  • 文档对这个方法的解释是:如果方法执行不了,可以给别人,也就是找个人来执行的意思

代码验证

  • 下面在脱离源码环境来验证下
// .h
@interface WSPerson : NSObject

- (void)sayLike; 

@end

// .m
@implementation WSPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {

    NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));

    return  [super forwardingTargetForSelector:aSelector];
}

// 调用
WSPerson *person = [[WSPerson alloc] init];
[person sayLike];

@end

  • 执行结果如下:

    截屏2021-07-08 17.38.11.png

  • 说明找不到方法会走到这里,文档里说,方法找不到,可以指定一个来执行,我们在WSTeacher里添加一个sayLike方法,并实现,然后将forwardingTargetForSelector的返回值,指定为WSTeacher对象:

// WSTeacher.h
@interface WSTeacher : NSObject

- (void)sayLike;

@end

// WSTeacher.m
@implementation WSTeacher
- (void)sayLike {
    NSLog(@"🎉🎉🎉 %s", __func__);
}
@end


// WSPerson.m
@implementation WSPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {

    NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));

    return [WSTeacher alloc];
}
@end

运行结果如下:

截屏2021-07-08 17.46.36.png

  • 这样就在WSTeacher类中找到了,不过这个过程有一个弊端,如果指定的这个类也没有实现这个方法怎么破?接下来就从快速转发进入慢速转发

慢速转发

在辅助日志msgSends中,我们能够看到,当forwardingTargetForSelector后面还有个methodSignatureForSelector方法,也就是快速转发没找到,就会进入methodSignatureForSelector方法

methodSignatureForSelector

  • 先来看看methodSignatureForSelector说明: 截屏2021-07-08 18.16.24.png
  • 文档上说,这个方法要返回一个方法签名NSMethodSignature,而且在消息的转发期间,必须要创建NSInvocation,也就是实现forwardInvocation方法

代码验证

  • 先验证下代码是否能进来,首先要去掉WSTeacher中的sayLike实现:
@implementation WSPerson 

// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));
    return  [WSTeacher alloc];
}

// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"🍊🍊🍊 %s - %@", __func__, NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}
@end
  • 再来运行下看看 截屏2021-07-08 18.38.20.png

疑问:methodSignatureForSelector居然没走,这是为什么?

  • 再来分析下这段代码,发现forwardingTargetForSelector指定了WSTeacher去执行查找,但WSTeacher中并没有实现这些,所以就不会走,可以去掉快速转发再尝试:

截屏2021-07-09 08.49.32.png 代码果然和预期一样走进来了,在文档上说,需要返回方法签名和实现forwardInvocation方法,我们修改下这里面代码:

// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"🍊🍊🍊 %s - %@", __func__, NSStringFromSelector(aSelector));
    if (aSelector == @selector(sayLike)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

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

运行结果如下:

截屏2021-07-09 09.10.49.png 这样就避免了崩溃forwardInvocation中保存了方法签名信息。NSInvocation也可以处理相关的事务,例如指定实现

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    WSTeacher *t = [WSTeacher alloc];
    if ([self respondsToSelector:anInvocation.selector]) {
        [anInvocation invoke];
    } else if ([t respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:t];
    } else {
        anInvocation.target = t;
        anInvocation.selector = @selector(sayLearn);
        [anInvocation invoke];
        NSLog(@" 方法找不到 ");
    }
}
  • forwardInvocation可以指定给某个类去调用,也可以改变自己的target,和selector
  • forwardInvocation流程相当于它告诉程序sayLike这个方法还在,程序就不会去做异常处理,但实际是存在的,也可以自主的做去处理。不过这一系列的流程下来,会造成资源的浪费。

总结

  • 快速转发:调用forwardingTargetForSelector实现快速转发流程,可以返回自己想要替自己实现的对象去实现该方法,如果返回nil将进入慢速转发流程。
  • 慢速转发:慢速转发需要调用methodSignatureForSelectorforwardInvocation共同实现,methodSignatureForSelector需要返回方法签名,而forwardInvocation里面可以做一些处理,例如指定对象处理,指定方法等。如果 没有方法签名,则结束慢速查找。

消息转发图解

截屏2021-07-09 10.39.08.png

查看堆栈

  • 假如这些转发流程都没有处理,运行肯定是报错的,那么怎么查看呢,我们可以用bt命令打印堆栈:

截屏2021-07-09 11.06.10.png
通过分析得知,在doesNotRecognizeSelector之后还走两个方法___forwarding_____forwarding_prep_0___方法,这两个方法都在CoreFoundation库中,按照以往的经验去源码中分析下作用,但是源码中并没有找到,说明CoreFoundation库并没有完全开源,那怎么搞?我们可以去查看下动态库。

  • 先通过image list命令找到动态库路径,然后找到CoreFoundation动态库

截屏2021-07-09 11.10.22.png

  • 然后使用Hopper或者IDA去分析,本文主要在Hopper中分析

反汇编(Hopper)

分析

将找到的CoreFoundation动态库拖到Hopper

截屏2021-07-09 11.22.49.png

  • 然后点击左侧label搜索__forwarding_prep_0___,主要查看伪代码

截屏2021-07-09 11.36.59.png

  • 我们看到__forwarding_prep_0___中调用___forwarding___后得到rax,双击进入___forwarding___方法:

截屏2021-07-09 13.58.56.png

  • 这里显示判断forwardingTargetForSelector有没有实现,如果没有实现就会走loc_64ad7,也就是慢速转发:

截屏2021-07-09 14.02.45.png

  • 在慢速转发时,先判断是不是僵尸对象_NSZombie_,再判断methodSignatureForSelector是否实现,没有实现就走loc_64e47
  • 如果methodSignatureForSelector返回值为nil,则走loc_64eac,也就是报错
  • 如果methodSignatureForSelector有返回值,则会去判断forwardStackInvocation方法是否存在,然后进行后面的处理

总结

  • hopper也可以查看流程,只不过流程看起来没那么舒服要慢慢的去分析,需要慢慢的去分析,最后还是能够分析处理转发流程。