【iOS底层】11:消息转发

一、msgSend消息发送监听

在探索了很多次了lookUpImpOrForward方法中,查找完成后会写入cache

在写入cache中发现有个打印log的操作

 

 我们来看下是否可以通过这个输出到本地的日志,是否可以查看到一些不一样的流程和我们还没探索过的方法。

上图中看到如果想要调用logMessageSend方法,需要满足判断条件:objcMsgLogEnabled && implementerimplementer是我们传入的class,那么影响判断的就只有objcMsgLogEnabled这一个条件了。

objcMsgLogEnabled默认是flase,全局搜索一下发现赋值在instrumentObjcMessageSends方法里。

那我们调用下试试,是否可以打印出日志。

由于源码工程跑不了这个方法,我们新建一个工程来跑。

发现还是报错,正常,那么我们在电脑里全局搜索一下是否有msgSend-xxx文件。

 打开看一下:

这些方法没有探索过,我们逐个探索。

二、消息快速转发

Xcode中shift+command+0,打开文档,看下forwardingTargetForSelector的官方解释。

- (id)forwardingTargetForSelector:(SEL)aSelector;

作用:返回无法识别的消息应首先指向的对象。

讨论:如果一个对象实现(或继承)此方法,并返回一个非nil(和非self)结果,则该返回的对象将用作新的接收方对象,消息分派将继续到该新对象。(显然,如果从这个方法返回self,代码将陷入无限循环。)
如果你在非根类中实现这个方法,如果你的类对于给定的选择器没有返回任何东西,那么你应该返回调用super的实现的结果。
该方法使对象有机会在昂贵得多的forwardInvocation:机制接管之前,重定向发送给它的未知消息。当您只想将消息重定向到另一个对象,并且比常规转发快一个数量级时,这很有用。如果转发的目标是捕获NSInvocation,或者在转发期间操作参数或返回值,那么它就没有用了。

相当于重定向。来试一下效果。

@interface LGPerson : NSObject
- (void)sayInstanceMethod;
@end

@implementation LGPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s---%@", __func__, NSStringFromSelector(aSelector));
    return [LGStudent alloc];
}

@end

@interface LGStudent : NSObject
- (void)sayInstanceMethod;
@end

@implementation LGStudent

- (void)sayInstanceMethod{
    NSLog(@"%s",__func__);
}
@end

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

输出:

-[LGPerson forwardingTargetForSelector:]---sayInstanceMethod
-[LGStudent sayInstanceMethod]

成功转发到LGStudent,并调用。 

三、 消息慢速转发

如果我不实现forwardingTargetForSelector:呢?再看下一个日志调用方法methodSignatureForSelector:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

methodSignatureForSelector:

作用:返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。

如果LGStudent中没实现目标实例方法呢?

打印输出:

-[LGPerson forwardingTargetForSelector:]---sayInstanceMethod
-[LGStudent sayInstanceMethod]: unrecognized selector sent to instance 0x103805450

发现还报错,而且并没有走methodSignatureForSelector方法,因为调用forwardingTargetForSelector方法的时候就已经重定向到LGStudent里了,就不会继续走LGPerson类里的流程了。我们把forwardingTargetForSelector方法注释掉。

注释以后发现还会报错!

查看methodSignatureForSelector的官方文档后发现,需要和另一个方法搭配使用 ,才能实现消息转发。

- (void)forwardInvocation:(NSInvocation *)anInvocation;

作用:被子类重写以将消息转发到其他对象。

重要:为了响应对象本身不能识别的方法,除了forwardInvocation:之外,还必须覆盖方法signatureforselector:。转发消息的机制使用从methodSignatureForSelector:获得的信息来创建要转发的NSInvocation对象。您的覆盖方法必须为给定的选择器提供适当的方法签名,方法是预先确定一个或向另一个对象请求一个。

实现一下:

 发现还是报错,头疼~

其实是因为我们没有对methodSignatureForSelector签名做处理,即返回值。

这里的返回值中的type是一个签名 ,【iOS底层】05:类原理分析(下)有讲到。

打印:

-[LGPerson methodSignatureForSelector:]---sayInstanceMethod
-[LGPerson forwardInvocation:]---<NSInvocation: 0x104019f50>

看出这两个方法都执行了,也没有crash了,但是我们的sayInstanceMethod并没有执行~而且此时已将LGStudent中的sayInstanceMethod方法实现的注释放开了。

出现此问题的原因在于:经过cache查找、methodlist慢速查找,消息快速转发之后均未找到对应imp,那么系统认为是找不到了,那么又不能让程序crash,那么就hold住了,但是什么也不做。

如果我们需要继续走下去呢?

就涉及到在forwardInvocation方法里继续操作了~

可以看到anInvocation里存着receiverimp方法,以待开发者再进行处理,默默等候。

四、forwardInvocation继续探索

既然forwardInvocation静候开发者操作,那么我们可以进行怎么操作呢?

我们还可以这样处理,转发到LGStudent,调起它的sayInstanceMethod方法~

五、反汇编CoreFoundation

上边的操作似乎有点上帝视角,那么我们不做哪些操作,在程序crash之后再lldb中bt查看崩溃前的堆栈,发现了我们上边没探索的doseNotRecognizeSelector方法,它是在CoreFoundation库里的,但不幸的是苹果并没有对这个库开源,文档中也没有寻找到有用的信息,那么我们有什么办法来探索这个方法呢?

分析CoreFoundation可执行文件!

这个通过编译程序lldb输入image list,查找到CoreFoundation的地址,然后在磁盘里路径下找~

这里就要用到一个工具软件:Hooper

打开后是介个亚子滴~

  • 先来看___forwarding___:

这里要用这种模式查看。

双击红框代码进入。

往下找就找到了forwardingTargetForSelector:如果未响应,则跳转loc_64a67

如果methodSignatureForSelector:实现了继续往下走到forwardInvocation:

如果methodSignatureForSelector:没找到就执行了doesNotRecognizeSelector:

至此也印证了我们再msgSends-xxx本地日志文件记录的流程。 

六、IDA反汇编

我们在windows上电脑上安装一个IDA,将CoreFoundation文件拖入IDA软件。

双击这里以后按F5进入Pseudocode-A伪代码模式。

也可以看到相应的流程方法~小伙伴们有时间可以深入去探索~

七、动态决议方法为什么走两次

上篇文章我们遗留了一个问题,为什么打印的时候resolveInstanceMethod:方法调用了两次。

resolveInstanceMethod里打断点,第二次的时候lldb输入bt查看堆栈。

我们在resolveInstanceMethod打个断点看看。

第一次,很好理解,正常的消息发送未找到对应的imp,执行__objc_msgSend_uncached,进入消息转发流程lookUpImpOrForward

第二次,出现了我们见过的_CF_forwarding_prep_0、___forwarding___.

 

methodSignatureForSelector中调用了__methodDescriptionForSelector__methodDescriptionForSelector调用了class_getInstanceMethod方法,最终又调到了lookUpImpOrForward。这样才走了第二次~

总结、

消息转发流程图:

先欠着~

至此消息转发流程我们已经探索完了,期待下一篇文章~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值