美文网首页
iOS底层原理13:消息转发流程

iOS底层原理13:消息转发流程

作者: 黑白森林无间道 | 来源:发表于2021-07-19 13:45 被阅读0次

iOS底层原理12:动态方法决议中探究了动态方法决议。在动态决议之后,通过日志辅助功能认识到forwardingTargetForSelectormethodSignatureForSelector方法,也就是消息发送的最后一个流程消息转发

准备工作

  • objc4-818 源码
  • CF源码
  • 反汇编工具Hopperida

消息转发

消息发送在经过动态方法决议后,仍然没有查找到正真的方法实现,此时进入消息转发流程。转发流程分两步快速转发慢速转发

快速转发流程

通过日志辅助发现,在崩溃之前会执行forwardingTargetForSelector方法,即消息快速流程

forwardingTargetForSelector方法探究

打开Xcode,通过快捷键command + shift + 0打开开发者文档,然后搜索forwardingTargetForSelector,结果如下图

image
  • 根据开发者文档的描述,forwardingTargetForSelector返回了一个重定向对象,这个对象来响应未实现的方法。

代码验证

  • 新建一个iOS工程,创建两个类HTPersonHTCommon
    • HTPerson类 只有实例方法sayHello类方法sayBye的声明,无实现
    • HTCommon类 实现了这两个方法
image
  • HTPerson中添加forwardingTargetForSelector方法,代码如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(sayHello)) {
        return [[HTCommon alloc] init];
    } else if (aSelector == @selector(sayBye)) {
        return [HTCommon class];
    }
    return [NSObject alloc];
}
  • 运行程序,对象方法sayHello已经成功调用了,但是类方法依然会导致崩溃
image

【问题】如何通过消息转发快速流程,来处理类方法呢?这里猜测需要通过+ (id)forwardingTargetForSelector:(SEL)aSelector {} 来处理类方法

  • 继续修改forwardingTargetForSelector方法,代码如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(sayHello)) {
        return [[HTCommon alloc] init];
    }
    return [NSObject alloc];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(sayBye)) {
        return [HTCommon class];
    }
    return [NSObject class];
}
image

慢速转发流程

如果通过快速转发流程forwardingTargetForSelector还是找不到方法实现,接下来苹果还给了我们一次机会,即慢速转发流程

  • 慢速转发流程methodSignatureForSelector,查看文档如下:
image
  • methodSignatureForSelector方法返回的是NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。methodSignatureForSelector一般和forwardInvocation搭配使用,如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation

代码验证

#pragma mark- 处理对象方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
    if (aSelector == @selector(sayHello)) {
        return [NSMethodSignature signatureWithObjCTypes:"v:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector));
    
    if (anInvocation.selector == @selector(sayHello)) {
        HTCommon *common = [[HTCommon alloc] init];
        anInvocation.target = common;
        return [anInvocation invoke];
    }
    return [super forwardInvocation:anInvocation];
}

#pragma mark- 处理类方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
    if (aSelector == @selector(sayBye)) {
        return [NSMethodSignature signatureWithObjCTypes:"v:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

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

如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation方法对anInvocation事务进行处理,如果不处理也不会报错

消息转发总结

消息转发的处理主要分为两部分:

  • 快速转发】当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法
    • 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程
    • 如果返回nil,则进入慢速消息转发
  • 慢速转发】执行到methodSignatureForSelector方法
    • 如果返回的方法签名为nil,则直接崩溃报错
    • 如果返回的方法签名不为nil,走到forwardInvocation方法中,对anInvocation事务进行处理,如果不处理也不会报错

方法调用流程

image

总结

至此,objc_msgSend发送消息的流程就分析完成了,我们可以得出整个方法调用的流程:

  • 快速查找流程】:在类的缓存cache中查找指定方法的实现
  • 慢速查找流程】:如果缓存中没有找到,则在类的方法列表中查找(二分查找),如果还是没找到,则去父类链的缓存和方法列表中查找
  • 动态方法决议】:如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法
  • 消息转发】:如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
  • 如果转发之后也没有,则程序直接报错崩溃unrecognized selector sent to instance

缓存cache快速查找流程 --> 慢速查找流程 --> 动态决议方法resolveInstanceMethod --> 快速转发流程forwardingTargetForSelector --> 慢速转发流程(methodSignatureForSelector) --> resolveInstanceMethod --> forwardInvocation --> 崩溃报错

补充

hopper反汇编CoreFoundation系统库

查看崩溃时的堆栈信息,调用了CoreFoundation系统库的forwarding_prep_0___forwarding___方法,如下图

image

下载CoreFoundation源码,并没有找到这两个方法的实现,说明这块内容苹果并没有对外提供,只是开源了部分CoreFoundation源码

  • 通过image list获取所有的镜像文件列表,找到CoreFoundation库的文件路径
image
  • 通过objdump --macho --syms CoreFoundation | grep "forwarding" 查看 CoreFoundation库的符号表,发现___forwarding_prep_0_______forwarding___都是本地符号
image

forwarding_prep_0方法

全局搜索__forwarding_prep_0___,发现只有一个,且会调用__forwarding__

image

____forwarding___方法

  • 快速转发流程
    • 如果forwardingTargetForSelector方法没有实现,跳转 loc_115baf流程
    • 如果forwardingTargetForSelector方法的返回值是nil,跳转 loc_115baf流程
image
  • 慢速转发流程

    • 如果methodSignatureForSelector没有实现直接跳转到loc_115f4a流程,最终会进入loc_115fc5流程
    • 如果methodSignatureForSelector返回值等于nil跳转到loc_115fc5流程
    • 如果methodSignatureForSelector返回了签名信息的对象,则会调用_forwardStackInvocation:方法,最后会执行forwardInvocation方法
      image
  • 慢速流程如果没有实现的话,则会进入doesNotRecognizeSelector:方法

image
  • doesNotRecognizeSelector主要就是对崩溃信息的处理,以及输出报错信息
image

相关文章

网友评论

      本文标题:iOS底层原理13:消息转发流程

      本文链接:https://www.haomeiwen.com/subject/czacmltx.html