
iOS--OC底层原理文章汇总
在前面两章中详情了方法消息的解决流程,宏观上来说,方法的本质就是对消息的发送,解决消息的过程呢,我们经历了objc_msgSend快速查找、慢速查找。在前面两个环节中,仍然在本类、父类继承链、元类继承缓存中找未找到消息,又未采取动态方法决议,对未查找到的方法实现resolveInstancMethod,则就会报错奔溃。这样对于开发者来说是不愿看到的,所以对消息的解决就来到了新的层次,进行消息转发,本章内容将围绕这个开展。
通过前面分析lookUpImpOrForward,既然是寻觅或者者转发,那在没寻觅到的情况下,它是怎样转发的呢?入口又是在哪?
instrumentObjcMessageSends分析方法调用顺序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.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对象。burnBook,但是不在forwardingTargetForSelector中解决。再实现方法签名方法,返回父类方法,程序继续奔溃,但是也表示我们的方法按照log中的顺序走下来了。
快速转发->方法签名forwardInvocation:
结果-不崩溃方法签名图
image.png
我们可以查看下NSInvocation结构
NSInvocation定义anInvocation中有哪些东西
签名后的anInvocation- (void)forwardInvocation:(NSInvocation *)anInvocationEnglish
慢速消息转发给English我们发现和我们之前分析的log文件中方法调用顺序不一致,那么我们可以再做一个操作,实现resolveInstanceMethod,打印结果,我们就能发现,与msgSends-10393中的方法执行顺序的一致

经过一系列的转发,我们可以大致总结到以下一个流程
消息转发流程