iOS-底层原理09 消息转发

851

在上一文章中,如果最后方法决议还是获取不到IMP,那么会进行消息的转发流程。 未命名文件(3).png

通过输出文件查看消息的转发流程

在方法执行后都会走缓存的方法。这里有一个设置是否输出到文件的方法,如下所示。可以通过修改objcMsgLogEnabled 控制是否输出

这里是缓存方法,log_and_fill_cache方法是缓存的方法,点进去查看具体实现

截屏2021-07-12 下午4.16.15.png

可以看出objcMsgLogEnabled 可以决定是否输出文件,设置成YES的话可以获取。全局搜索,可以看到objcMsgLogEnabledinstrumentObjcMessageSends有关系,如果instrumentObjcMessageSends设置YES的话,objcMsgLogEnabled也会为YES。

截屏2021-07-12 下午4.20.19.png

案例

//用来改变objcMsgLogEnabled的值
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        instrumentObjcMessageSends(YES);
        [LGPerson sayHello];
        instrumentObjcMessageSends(NO);
        
    }
    return 0;
}

运行报错之后,前往文件/tmp/msgSends。可以看出找不到慢速查询之后找不到IMP,执行动态方法决议 -> 快速消息转发 -> 慢速消息转发 -> 第二次动态方法决议。

//类方法决议
+ HTPerson NSObject resolveClassMethod:
+ HTPerson NSObject resolveClassMethod:
//查询元类中的实例方法决议
+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:
//消息的快速转发 返回对象
+ HTPerson NSObject forwardingTargetForSelector:
+ HTPerson NSObject forwardingTargetForSelector:
//消息的慢速转发 返回事务
+ HTPerson NSObject methodSignatureForSelector:
+ HTPerson NSObject methodSignatureForSelector:

+ HTPerson NSObject resolveClassMethod:
+ HTPerson NSObject resolveClassMethod:

+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:

+ HTPerson NSObject doesNotRecognizeSelector:
+ HTPerson NSObject doesNotRecognizeSelector:

+ HTPerson NSObject class

根据案例研究消息转发的流程以及关系

根据以上的方法列表的顺序,可以看出消息转发的流程 快速消息转发->慢速消息转发.分别探索他们的作用,以及特性。

快速消息转发

  • forwardingTargetForSelector返回一个可执行对象,会让他去执行对应的方法(第二次处理崩溃的地方
//快速消息转发
//返回一个执行对象 可以让他去执行
//可以在这里动态添加方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"forwardingTargetForSelector==%@=======",NSStringFromSelector(aSelector));
    return [OtherPerson alloc];
}
  • forwardingTargetForSelector不做任何处理,会进入到慢速消息转发。依然会崩溃
//快速消息转发
//返回一个执行对象 可以让他去执行
//可以在这里动态添加方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"forwardingTargetForSelector==%@=======",NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}
//慢速消息转发
//返回一个事务
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"methodSignatureForSelector==%@=======",NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"forwardInvocation");
}
  • 快速消息转发的意义,是在慢速转发前提供一次处理的机会,返回执行该方法的对象 或者 添加对应的方法。因为慢速转发消耗的资源更多。

慢速消息转发

如果快速消息转发没有做处理,会进入到慢速查询的方法中。增加forwardInvocation方法就不会产生崩溃。

//慢速消息转发
//返回一个事务
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"methodSignatureForSelector==%@=======",NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"forwardInvocation");
}
  • methodSignatureForSelector 返回一个方法的签名,这里是第三次处理崩溃的地方
  • forwardInvocation方法到了就不会崩溃,但是事务无法处理的话会浪费一个事务。所以这里可以交给特定对象或者进行其他的处理。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
    OtherPerson *person = [OtherPerson alloc];
    //如果自己可以处理,直接调用
    if ([self respondsToSelector:anInvocation.selector]) {
        [anInvocation invoke];
    }
    //判断OtherPerson是否可以处理,如果可以,直接调用
    else if ([person respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:person];
    }
    //没有任何人去实现,那么这里将错误上报
    else {
        NSLog(@"发生了错误的方法:%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
    }
}

反汇编

上述过程中看不到方法调用的流程,因为以上代码的调用均是在底层汇编进行实现的。我们通过反汇编查找底层实现的过程。获取底层的代码 [Souece Browser地址:opensource.apple.com/tarballs/CF…]

8158f5efbf6543dd90ae82d8433b6eba~tplv-k3u1fbpfcp-watermark.image.png

  • 反汇编工具 hopper和IDA。下载地址hopper官网地址 Hopper和IDA是一个可以帮助我们静态分析可视性文件的工具,可以将可执行文件反汇编成伪代码、控制流程图等,下面以Hopper为例(注:hopper高级版本是一款收费软件,针对比较简单的反汇编需求来说,demo版本足够使用了)
  • 打开hopper工具 2251862-5614d16722f20e3f.png

2251862-14e3bac8cb80109e.png

  • 从崩溃方法可以崩溃的方法在coreFoundation框架下。使用image list 获取到静态文件地址如下所示,拖到反汇编程序中。 截屏2021-07-13 上午11.30.56.png
  • 获取到目录下的静态文件,进行反汇编 2251862-d8145f81f812dd08.jpg
  • 反汇编类目如下所示 2251862-8e5a19034e2fc9e9.png
  • 搜索forwarding进行并点进去查看汇编伪代码 2251862-d1eea35429b42c71.png
  • forwardingTargetForSelector方法。以下是___forwarding___的伪代码实现,首先是查看是否实现forwardingTargetForSelector方法,如果没有响应,跳转至loc_6459b即快速转发没有响应,进入慢速转发流程 2251862-b6116c32477e5ec4.png
  • methodSignatureForSelector方法 2251862-fc98b24257514987.png
  • forwardINvocation方法。如果methodSignatureForSelector返回值不为空,则在forwardInvocation方法中对invocation进行处理 2251862-e20ca7ad3d5abe6d.png

参考文章

Style_月月——消息流程分析之 动态方法决议 & 消息转发
011-消息的动态转发流程