OC 原理探索:消息转发流程

273

前言

上一篇 OC 原理探索:动态方法决议 中我们进行了动态方法决议的探索,但是如果还是没有处理呢,后面还会做什么吗,今天来探索动态方法决议后面的流程。

准备工作

一、消息转发流程引入

我们先进入lookUpImpOrForward函数,定位到动态方法决议处的代码。

image.png

  • 可以看到动态方法决议后面已经没有代码了,剩下的只是goto的代码和return相关了。
  • 流程已经走完了,接下来该怎么探索呢,我们发现有个log_and_fill_cache函数,点击进去看一下。

instrumentObjcMessageSends 函数引入

log_and_fill_cache函数中又调用了logMessageSend

image.png

  • 我们可以看到logMessageSend函数中打印了一些+-方法和地址,还有/tmp/msgSends-这种类似沙盒目录相关的操作。
  • 但是在log_and_fill_cache函数中可以看到,只有objcMsgLogEnabledtrue时才能调用logMessageSend,那么探索一下什么时候objcMsgLogEnabledtrue

全局搜索objcMsgLogEnabled

image.png

  • 经过搜索,我们发现只有这一个地方可以让objcMsgLogEnabled的值为true,所以接下来我们用extern修饰instrumentObjcMessageSends,让外部可以访问。

instrumentObjcMessageSends 函数使用

在项目中添加下面的代码,运行程序:

@interface SSLPerson : NSObject
- (void)say1;
@end

@implementation SSLPerson
@end

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SSLPerson *person = [SSLPerson alloc];
        instrumentObjcMessageSends(YES);
        [person say1];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

image.png

  • 工程依旧会报错,我们去沙盒目录看一下是不是真的有文件写入。

打开访达 -> cmd + shift + g ->

image.png

  • 在沙河目录中我们找到了msgSends-文件,打开文件看看文件中都有什么。

image.png

  • 在文件中,我们看到resolveInstanceMethod:后边又调用了很多函数,紧接着调用的是forwardingTargetForSelector:函数和methodSignatureForSelector:函数,我们接下来对两个函数分别进行探索。

二、消息快速转发流程

我们先来对forwardingTargetForSelector:函数进行探索,cmd + shift + 0 打开documentation,然后进行搜索,可以得到下面的结果。

image.png

  • 通过阅读文档,我们可以知道forwardingTargetForSelector:函数有重定向的作用,可以指定一个对象去完成没有实现的方法,我们到项目中去实践一下。

新创建一个SSLDirector类,并在SSLPerson中添加forwardingTargetForSelector方法实现。

@interface SSLPerson : NSObject
- (void)say1;
@end

@implementation SSLPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return [SSLDirector alloc];
}
@end


@interface SSLDirector : NSObject
- (void)say1;
@end

@implementation SSLDirector
- (void)say1
{
    NSLog(@"SSLDirector say1");
}
@end

运行程序查看结果:

image.png

  • SSLDirector say1成功打印,程序没有报错,这种解决方式还是很快速的,但是如果这种方式还是没有解决呢,下面继续探索。

三、消息慢速转发流程

methodSignatureForSelector

cmd + shift + 0 打开documentation,搜索methodSignatureForSelector:,可以得到下面的结果。

image.png

  • 通过阅读文档,我们了解到方法是通过返回方法签名NSMethodSignature,以及和forwardInvocation:方法联合使用,接下来用代码去实现。

SSLPerson中添加methodSignatureForSelectorforwardInvocation:方法实现。

@interface SSLPerson : NSObject
- (void)say1;
@end

@implementation SSLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(say1)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    
}
@end

运行程序查看结果:

image.png

  • 程序没有崩溃,也没有调用方法。
  • OC中调用方法是消息的发送,也可以说是事务事务可做可不做,不做的话也不会报错,接下来我们看一下forwardInvocation中可以做些什么。

forwardInvocation

cmd + shift + 0 打开documentation,搜索forwardInvocation:,可以得到下面的结果。

image.png

  • 阅读文档,我们下面按照文档再去项目中实现。

我们在forwardInvocation:方法中添加下面的代码,运行程序:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL aSelector = [anInvocation selector];
    
    SSLDirector *director = [SSLDirector alloc];
    
    if ([director respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:director];
    }   
}

image.png

  • SSLDirector say1正常打印,程序没有报错。

四、hopper 反汇编 CF

我们重新回到消息转发流程之前,不使用instrumentObjcMessageSends函数,换一种方式进行分析。

先运行程序:

image.png

  • 因为我们什么都没有做,所以程序还是会报unrecognized selector的错误。
  • 通过堆栈的打印,我们发现调用了___forwarding____CF_forwarding_prep_0,它们都属于CoreFoundation,我们去它的源码中找一下。

CF 源码探索

点击 CF 源码 进行下载,搜索forwardingprep_0

image.png

  • 如上图,结果是什么也没有搜到,这说明源码并没有开放这部分内容,接下来用反汇编的方式继续探索。

hopper 反汇编 CF

先安装好Hopper,准备好可执行CoreFoundation动态库:

image.png

CoreFoundation动态库拖到Hopper中打开,全局搜索forwarding:

image.png

  • 如上,可以看到___forwarding_prep_0_______forwarding___相关代码。

1. 反汇编 forwardingTargetForSelector:

点击____forwarding___

image.png

  • 如上图定位代码,判断类是否响应了forwardingTargetForSelector:,如果响应了就回去执行,如果没响应就会跳转到loc_64a67

2. 反汇编 forwardInvocation:

我们来看下loc_64a67的相关代码:

image.png

  • loc_64a67开始,会先判断对象是不是_NSZombie_(野指针),如果是野指针跳转到loc_64dc1
  • 如果不是野指针,判断类是否相应了methodSignatureForSelector:,如果没有响应跳转到loc_64dd7,如果响应了执行这个函数,并继续向下执行。
  • 碰到了_forwardStackInvocation:,这个是系统内部的方法,没有对外暴露,继续向下执行。
  • 到了loc_64c19,判断类是否相应了forwardInvocation:,如果没有响应跳转到loc_64ec2,如果响应了执行这个函数。

我们通过反汇编的方式,证明了消息转发在底层,是有这很明确的流程的,是有据可依的,另外除了HopperIDA也可以进行反汇编的探索。

五、消息转发流程图

image.png