OC-底层原理09—消息转发流程
iOS–OC底层原理文章汇总
在前面两章中详情了方法消息的解决流程,宏观上来说,方法的本质就是对消息的发送,解决消息的过程呢,我们经历了objc_msgSend快速查找、慢速查找。在前面两个环节中,仍然在本类、父类继承链、元类继承缓存中找未找到消息,又未采取动态方法决议,对未查找到的方法实现resolveInstancMethod,则就会报错奔溃。这样对于开发者来说是不愿看到的,所以对消息的解决就来到了新的层次,进行消息转发,本章内容将围绕这个开展。
铺垫
通过前面分析lookUpImpOrForward,既然是寻觅或者者转发,那在没寻觅到的情况下,它是怎样转发的呢?入口又是在哪?
- 通过
instrumentObjcMessageSends分析方法调用顺序
换一个思路,既然动态决议后,假如没有对Imp进行操作,就会崩溃,那可以通过该方法检测奔溃时方法的调用情况。lookUpImpOrForward -> log_and_fill_cache -> logMessageSend,objcMsgLogEnabled =YES是进入这个流程的关键.
//static voidlog_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer){#if SUPPORT_MESSAGE_LOGGING if (slowpath(objcMsgLogEnabled && implementer)) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; }#endif cache_fill(cls, sel, imp, receiver);}//-------------------------------------------------bool objcMsgLogEnabled = false;static int objcMsgLogFD = -1;bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector){ char buf[ 1024 ]; // Create/open the log file if (objcMsgLogFD == (-1)) { snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ()); objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid()); if (objcMsgLogFD < 0) { // no log file - disable logging objcMsgLogEnabled = false; objcMsgLogFD = -1; return true; } } // Make the log entry snprintf(buf, sizeof(buf), "%c %s %s %s\n", isClassMethod ? '+' : '-', objectsClass, implementingClass, sel_getName(selector)); objcMsgLogLock.lock(); write (objcMsgLogFD, buf, strlen(buf)); objcMsgLogLock.unlock(); // Tell caller to not cache the method return false;}// 1: objcMsgLogEnabled 控制开关// 2: externvoid 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;}// SUPPORT_MESSAGE_LOGGING#endif/tmp/这个路径就是就是将奔溃日志输出到本地临时缓存了,
在main.m中调用Book的burnBook的未实现方法,extern:这是一个关键字,是告诉编译器在编译时不要报错,在该类中不存在的方法,请去别的类查找。
objcMsgLogEnabled = true
使得objcMsgLogEnabled =true,则即可以查看到在本地生成的一个文件。调用仍然会奔溃,但log会输出的。
msgSends文件路径
文件内容-调用方法顺序
其中forwardingTargetForSelector就是快速转发方法;慢速转发则是methodSignatureForSelector,其实还搭配forwardInvocation使用。
消息转发-快速转发
我们该怎样使用forwardingTargetForSelector 呢?这个时候可以瞄一瞄苹果文档
forwardingTargetForSelector苹果说明
假如有无法识别的消息,就将其转发到指定的对象。那我们即可以进行一个操作,再定义一个English的类。假如识别到burnBook被调用,我们就将其转发给另外一个类的方法中,English类中实现了这个方法,就会在English中找寻这个方法。
// English.m-(void)burnBook{ NSLog(@"English burn book");}Book中将方法转发出去,把目标对象返回。
#import "Book.h"#import "English.h"@implementation Book- (id)forwardingTargetForSelector:(SEL)aSelector{ if ([NSStringFromSelector(aSelector) isEqualToString:@"burnBook"]) { return [English alloc]; } return [super forwardingTargetForSelector:aSelector];}@end转发到English,English实现了该burnBook方法,最终会打印出结果,也不会报错。
转发消息到其余对象
这样我们即可以在开发过程中很好的利用这样一点,结合动态方法决议,在需要的地方增加方法,避免应用奔溃,或者者利用运行时动态执行少量自己设置方法都是很好的方向。
消息转发-慢速转发
在快速转发消息之后,就会来到慢速转发(标准转发)消息。找到运行时的方法methodSignatureForSelector。
苹果解释
返回方法的签名,在苹果的文档中解释了方法的使用场景,也指出在转发消息时要创立NSInvocation对象。
我们先验证下log里面方法调用顺序,仍然是调用burnBook,但是不在forwardingTargetForSelector中解决。再实现方法签名方法,返回父类方法,程序继续奔溃,但是也表示我们的方法按照log中的顺序走下来了。
快速转发->方法签名
现在,我们对其进行签名,并实现forwardInvocation:
结果-不崩溃
经过慢速转发,程序已经不再奔溃,它已经将消息转发出去,自己也不再解决。
方法签名图
image.png
我们可以查看下NSInvocation结构
NSInvocation定义
我们可以验证下签名之后的anInvocation中有哪些东西
签名后的anInvocation
表明签名后的信息都传递到了- (void)forwardInvocation:(NSInvocation *)anInvocation
我们可以在将其消息转发给English
慢速消息转发给English
消息转发流程
我们发现和我们之前分析的log文件中方法调用顺序不一致,那么我们可以再做一个操作,实现resolveInstanceMethod,打印结果,我们就能发现,与msgSends-10393中的方法执行顺序的一致

经过一系列的转发,我们可以大致总结到以下一个流程
消息转发流程
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » OC-底层原理09—消息转发流程
image.png