阅读 62

OC方法底层探索——消息的转发流程

前言

之前已经讲完了消息的快速和慢速查找流程,那么接下来消息是如何做处理的呢?本章节主要讨论的就是消息接下来的处理,消息的转发流程。

InstrumentObjcMessageSends方法

再次回到lookUpImpOrForward方法中,也是通过不断地查找和摸索,发现了在插入的过程中,也就是log_and_fill_cache的方法里,有一个判断参数叫做objcMsgLogEnabled,其实看参数名字就知道,这个判断是关于Msglog的是否打印的开关,那么全局搜这个开关,就可以看到有给他赋值的地方,也就是instrumentObjcMessageSends方法可以控制他关于是否打印。

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

复制代码

于是可以在自己的类中尝试写一个这样的方法,并且作为开关监听这个方法的整个实现机制,所有的日志打印目录在/tmp/msgSends-,搜索的时候后面加个-。

1625311920972.jpg

在日志文件中,可以看到接下来走了forwardTargetForSelector方法,打开苹果的api(command+shift+0 是0不是o!)定义是:Returns the object to which unrecognized messages should first be directed.返回一个无法识别的消息的对象在第一次会被重定向。原话:This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.

快速转发流程 forwardingTargetForSelector

该方法使对象有机会在昂贵得多的forwardInvocation:机制接管之前,重定向发送给它的未知消息。当您只想将消息重定向到另一个对象,并且比常规转发快一个数量级时,这很有用。如果转发的目标是捕获NSInvocation,或者在转发期间操作参数或返回值,那么它就没有用了(来自有道翻译)

简而言之,如果只是重定向,那么这个方法很管用,如果你想做具体的操作,可以使用forwardInvocation。

举例

给LGPerson的对象发送一个消息,这个消息在LGPerson类中没有,在LGStudent中定义了,给LGPerson的对象方法添加forwardingTargetForSelector,并在消息转发的时候,给他重新赋予有个这个方法的对象看是否会报错。

截屏2021-07-03 下午6.31.48.png

截屏2021-07-03 下午6.32.02.png

截屏2021-07-03 下午6.32.07.png

很明显打印出来123.

应用

可以新增一个背锅类,所有的关于方法找不到的问题都可以通过本类防止崩溃。

总结:

苹果为了满足运行时机制存在的可能的不稳定性,所以方法在经过了快速、慢速查找还是找不到的情况下,通过动态方法决议可以弥补,如果这一层没有做,下一层,可以告诉系统,谁可以做,谁来做。

慢速转发流程 methodSignatureForSelector

如果快速转发流程没有做处理,那么就会进入到methodSignatureForSelector,也就是慢速转发流程,可以通过之前的崩溃日志看到系统打印了这个方法。

定义:

Returns an NSMethodSignature object that contains a description of the method identified by a given selector.

返回一个方法的签名,并且这个方法跟forwardInvocation方法绑定(请一直拖到api的底部Related )。

注意

F1B5CD93-DEB2-4981-BFDB-83E9433C02F0.png

8CA9B03A-2E98-4B96-858F-1BB3EE221626.png

把LGStudent类里的方法注释,直接在LGPerson里写签名方法,发现控制台并没有打印签名方法,原因是,在LGStudent alloc的时候,会重新走一遍快速和慢速查找也会走一遍签名方法,但是在LGStudent类里并没有实现,所以会直接崩溃而不会继续走LGPerson的签名方法了,所以这里写返回nil即可。

举例

在LGPerson类中写入methodSignatureForSelector和forwardInvocation方法,系统不会崩溃也不会报错。

截屏2021-07-03 下午10.40.03.png

截屏2021-07-03 下午10.41.14.png

截屏2021-07-03 下午10.44.01.png

通过这种慢速转发的方式,也可以做到防崩溃并且可以灵活的决定方法要不要去实现,在forwardInvocation方法中也保存了sel的信息.

举例2

上面的例子并没有对forwardInvocation这个方法做处理,所以其实慢速转发收到以后其实什么也没有做,那么这个方法到底可以做什么呢?看一下第二个例子。 有两个类,NXPerson和NXStudent类,在main方法中调用NXPerson中的sayNX这个方法,但是这个方法没有实现,只是在NXStudent类中实现了,那么可以通过在NXPerson中通过NXStudent的实例化对象去调他的方法。

截屏2021-07-06 下午4.04.33.png

截屏2021-07-06 下午4.08.22.png

methodSignatureForSelector仍然是签名,慢速转发通过forwardInvocation方法,去完成方法的流转调用,首先是判断本身有没有实现,其次是判断我们的工具人类(老王类)有没有实现接盘的方法,这样就实现防护了。

doesNotRecognizeSelector

doesNotRecognizeSelector这个方法也可以获取崩溃信息,但是这一层貌似不能对崩溃做处理,于是好像也没什么用,只是单纯打印崩溃方法而已,重写意义也不大。。

常规探索

以上的探索都是基于我们找打了这个Instrument方法,并且很巧合的打印了日志,才发现其中调用的方法,从而找到了突破口,但是正常情况下,我们很难去发现,那么直接从崩溃来看能否找到呢?

正常bt是打印堆栈信息,在doesNotRecognizeSelector之前分别有打印了___forwarding___和_CF_forwarding_prep_0.

方式一:找到苹果的官方CF Api,并没有找到记录。 方式二:打开苹果的官方文档也没有记录。

截屏2021-07-07 上午9.58.29.png

既然方式一和方式二都不可行,那么只有最后的方式三,反汇编,通过可执行文件直接反汇编源码获取其中的流程,工具:hopper(Mac); 将coreFoundation拖到Hopper中,并全局搜索___forwarding___。点进来一定要选择伪代码,不要选汇编,不看汇编!不看汇编!不看汇编!!根本看不懂!!

6F66479C-3F57-41BB-A3B8-825807E98706.png

乖乖,前面讲到的方法,在这边都有判断。

4398F8DF-4F06-42D1-86A9-9689DA6827DF.png

41FDFC11-12A1-4114-ABBB-D665EAE2465C.png

B456AE06-E137-4EC6-9589-011A824D99AF.png

当然也有没有暴露的方法

2C90CD42-EDCC-40D5-A3A1-170021D93D66.png

关于resolveInstanceMethod方法走两次

在动态方法决议时,找不到方法时,通过控制台可以看到打印了两次方法,也就是走了两次resolveInstanceMethod方法,why?

解决:可以通过在调起的地方打下一个断点,第二次进来的时候,打印堆栈信息,可以看到后面又调起了___forwarding___,通过反汇编,可以看到这一步是系统调起的,并且这一步是在第一遍所有的消息转发完成以后,也就是说系统会帮你再查找一遍,防止你在消息转发的时候做了补充,这里后面再补图吧~~~

文章分类
iOS
文章标签