上一篇学习了消息查找的动态方法决议,这一篇主要来探讨如果开发者没有实现动态方法决议提供的两个方法,那么消息流程将会继续怎样执行。
这篇文章主要围绕着两个问题进行展开:
1、动态方法决议之后的流程是怎样引入的;
2、动态方法决议之后的具体流程是怎么样的;
动态方法决议之后的流程是怎样引入的
我们依旧回归源码,来到方法lookupImpOrForward动态方法决议完毕的部分,来看一下后面的代码怎么执行:
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
我们根据上面的代码了解在执行完毕resolveMethod_locked流程后,依旧没有实现动态方法决议提供的补救方法,也就是依旧查不到方法,此时返回的resolveMethod_locked是个nil,所以从lookupImpOrForward去查看后续流程的思路就断了。
此时我们借助查看系统日志的方式来了解下到底发生了什么事情,所以引入了一个方法instrumentObjcMessageSends。
instrumentObjcMessageSends介绍
作用:打印OC内部调用了哪些方法。
使用:1、声明外部函数 2、设置起点和终点。3、查看打印日志到/private/tmp/文件夹下,会发现msgSends-xxxx文件,打开查看。
我们通过这个方法来看一下系统具体的方法调用:
image.png
我这边生成了文件msgSends-83990:
可以看到在没有实现方法relsolveInstanceMethod和relsolveClassMethod之后系统调用了方法:forwardingTargetForSelector、methodSignatureForSelector、resolveInstanceMethod、doesNotRecognizeSelector。所以我们通过关键方法instrumentObjcMessageSends的系统日志打印来了解和引入查看系统的后续流程。
动态方法决议之后的具体流程是怎么样的
通过上面的学习,了解到动态方法之后的流程是forwardingTargetForSelector-> methodSignatureForSelector-> doesNotRecognizeSelector。也就是进入到了我们常说的消息转发流程。
forwardingTargetForSelector
通过官方文档可以了解到forwardingTargetForSelector的作用是:
Discussion
If an object implements (or inherits) this method, and returns a non-
nil
(and non-self
) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you returnself
from this method, the code would just fall into an infinite loop.)If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive
forward<wbr data-v-78dd96cf="">Invocation:
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.
直译一下:如果一个对象实现或者继承了这个方法,并且返回了空结果的时候,那么这个被返回的对象会被使用作为一个新的接受者,消息会重新派发给新的对象。(显然如果你返回self,那么代码将会进入无止境的循环。)
如果你实现了这个方法在不是基类中,如果你的类没有返回为这个对应的方法那么你应该返回调用父类的实现结果。
这个方法给了对象一次在执行非常珍贵的方法forward<wbr data-v-78dd96cf="">Invocation:
之前一次重定向一个未知消息发送的机会。这会非常有作用当你想重定向消息给另外一个对象并且会比常规的消息发送快一个数量级。它不适用于当目的是捕获NSInvocation,或者在发送过程中修改参数或者返回值。
methodSignatureForSelector
同样了解一下官方文档给的描述:
Discussion
This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.
直译一下:这个方法是用于协议的实现。这个方法也适用在场景当NSInvocation对象必须被创建,例如在消息发送。如果你的对象成为了代理或者可以持有消息那么它不直接处理实现,你应该重写这个方法并且返回一个合适的方法签名。
这个方法要结合方法** forwardInvocation**来看,因为在学习methodSignatureForSelector时官方文档下面提到了相关方法就是forwardInvocation,而且上面的描述也提到methodSignatureForSelector不能直接去处理消息,只是生成一个对象并且成为消息的代理,我理解这个地方的最终实现是由forwardInvocation方法完成的。所以我们来看一下forwardInvocation。
forwardInvocation
关于方法forwardInvocation文档的描述有点长,这个方法有两个任务:
-定位可以响应通过anInvocation编码的对象。对于所有的消息,这个对象不必相同。
-发送消息给使用anInvocation的对象。一个anInvocation会保留结果,并且运行时会取出并且转发这个消息给原始发件人。
doesNotRecognizeSelector
Discussion
The runtime system invokes this method whenever an object receives an aSelector message it can’t respond to or forward. This method, in turn, raises an NSInvalidArgumentException, and generates an error message.
运行时会抛出这个方法当一个对象接受到一个无法被响应或者发送的消息。这个方法,在执行时,会采集一个叫NSInvalidArgumentException的异常并且创造一个错误消息。
通过以上的流程,我们来总结一下动态方法决议之后的过程,也就是消息转发:
-先来到方法forwardingTargetForSelector,这个方法执行消息非常快速,并且如果不是在根类里面实现的话需要调用父类,不能返回自己,否则进入死循环。
-如果forwardingTargetForSelector没有进行方法重定向那么会来到方法methodSignatureForSelector,也就是方法签名,这个方法会返回一个NSMethodSignature对象,该对象遵守了NSInvocation的协议。该方法不负责具体的实现,最终的实现由协议方法forwardInvocation来完成。所以methodSignatureForSelector和forwardInvocation是成对出现的。
-如果以上都没有实现,那么会来到方法doesNotRecognizeSelector会发送一个错误,并且采集当前的报错信息。
以上为整个消息转发的流程。消息发送以及消息转发就完整了!
-------------------------------神奇的分割线-----------------------------------------------
最后附一张自己复习重新画了一遍整个消息部分的流程图,有问题欢迎提出我好修改👏🏻
objc_msgSend.png
网友评论