009:消息流程分析-动态方法决议&消息转发-[resolveInstanceMethod-forwardingTargetForSelector-methodSignatureForSelector-forwardInvocation]
问题
1:动态方法决议调用次数?
目录
1:消息发送的流程
2:动态方法决议
3: 消息转发
预备
正文
一:消息发送的流程
1:先去本类的缓存方法列表中查找,核心函数是CacheLookup
2:如果没有找到,就去本类的方法列表中查找,
3:如果当前方法列表还是没有,就通过 superClass
指针在继承链中一直向上循环去查找,一直找到根 NSObject
。在父类
的cache
和方法列表中进行慢速查找。lookUpImpOrForward
4:如果查找过程中遇到父类==nil
或者父类cache
中的imp=imp_forward,
没有找到,那么就进入 消息转发流程。
5:到了消息转发流程还是没有处理的话,那么就会报 unrecognized selector
错误。
二:动态方法决议
1:lookUpImpOrForward--消息转发值慢速查找
当一个方法没有实现时,也就是在 cache list
和其继承关系的 method list
中,没有找到对应的方法。这时会进入消息转发阶段,但是在进入消息转发阶段前,Runtime
会给一次机会动态添加方法实现。
我们可以通过重写 resolveInstanceMethod
和 resolveClassMethod
方法,动态添加未实现的方法。
其中第一个是添加实例方法,第二个是添加类方法。这两个方法都有一个 BOOL
返回值,返回 NO
则进入消息转发流程
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { 上面代码及 循环体 省略... /// 未找到实现。尝试方法解析器一次。 // No implementation found. Try method resolver once. if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }
2:resolveMethod_locked
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); ///判断当前是否是元类 if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] /// 不是元类 类对象 resolveInstanceMethod(inst, sel, cls); } else { // 是元类 // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); ///如果 整个继承链 都没有找到 当前类方法的实现 还需要 调用一次 实例方法动态方法决议 ///为什么 因为 元类的父类 是根源类(NSObject) /// 根元类的父类指向 类对象(NSObject) 实例方法存才类对象里 所以 需要调用一次 实例方法动态方法决议 /// 此时为nil 说明 根元类 也没有找到 又因根源类super指向了 NSObject 类对象 所以需要进行 一次 类对象的动态方法决议 if (!lookUpImpOrNil(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); }
这里就是我们需要研究的动态方法解析代码了。流程如下
- 如果我们调用的是实例方法,那么
cls
就不是元类,就会执行实例方法的动态决议。 - 如果我们调用的是类方法,那么
cls
就是元类,则会先调用类方法的动态解析。如果没有找到,我们还会调用实例方法的动态解析。这是调用元类的实例方法,根据继承链,会从根元类(元类的isa
会指向根元类)开始找,最终会找到NSObject
根类的resolveInstanceMethod
方法。 - 注意:类方法存储在元类里面是实例方法。
最后再调用lookUpImpOrForward
重新查询一次。重新进行慢速查找,如果有动态添加sel对应的imp,则存入缓存中。
3:resolveInstanceMethod
针对实例方法
调用,在快速-慢速查找均没有找到实例方法
的实现时,我们有一次挽救的机会,即尝试一次动态方法决议
,由于是实例方法
,所以会走到resolveInstanceMethod
方法,其源码如下
static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); // look的是 resolveInstanceMethod --相当于是发送消息前的容错处理 if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); //发送resolve_sel消息 // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls //查找say666 IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } }
1:首先创建一个resolveInstanceMethod SEL
2:将此sel
传进lookUpImpOrNil,又会进入
lookUpImpOrForward
慢速查找流程查找,在整个继承链的慢速查找看我们resolveInstanceMethod
方法
1、首先判断根类(NSObject)的子类
存不存在 resolveInstanceMethod
的方法编号(sel)
2、如子类 存在此方法编号 去 cache中通过 sel
查询imp
如果存在 返回imp
3、如果子类只存在了方法编号
(如空方法) 没有具体实现也就是imp
不为真 再次进行 整个继承链的遍历向上查找
4、肯定能找到 因为 NSObject 实现了 找到后并将其放入缓存
3:找到了 将其存入缓存 并返回imp
没有找到 返回 forward_imp
准备转发
4:resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); //判断resolveClassMethod if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) { // Resolver not implemented. return; } Class nonmeta; { //对元类的简单处理 mutex_locker_t lock(runtimeLock); nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); // +initialize path should have realized nonmeta already if (!nonmeta->isRealized()) { _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta); } } //开始发送消息 BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls //再次查找 IMP imp = lookUpImpOrNil(inst, sel, cls); //判断出去 if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } }
5:代码示例
1:在LGPerson
中重写resolveInstanceMethod类方法
,将实例方法say666
的实现指向sayMaster
方法实现,如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(say666)) { NSLog(@"%@ 来了", NSStringFromSelector(sel)); //获取sayMaster方法的imp IMP imp = class_getMethodImplementation(self, @selector(sayMaster)); //获取sayMaster的实例方法 Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster)); //获取sayMaster的丰富签名 const char *type = method_getTypeEncoding(sayMethod); //将sel的实现指向sayMaster return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; }
2:在LGPerson
类中重写resolveInstanceMethod方法,并将sayNB
类方法的实现指向类方法lgClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{ if (sel == @selector(sayNB)) { NSLog(@"%@ 来了", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return [super resolveClassMethod:sel]; }
3:优化
上面的这种方式是单独在每个类中重写,有没有更好的,一劳永逸的方法呢?其实通过方法慢速查找流程可以发现其查找路径有两条
- 实例方法:
类 -- 父类 -- 根类 -- nil
- 类方法:
元类 -- 根元类 -- 根类 -- nil
它们的共同点是如果前面没找到,都会来到根类即NSObject中查找
,所以我们是否可以将上述的两个方法统一整合在一起呢?答案是可以的,可以通过NSObject添加分类
的方式来实现统一处理
,而且由于类方法的查找,在其继承链,查找的也是实例方法,所以可以将实例方法 和 类方法的统一处理放在resolveInstanceMethod
方法中,如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(say666)) { NSLog(@"%@ 来了", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(self, @selector(sayMaster)); Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster)); const char *type = method_getTypeEncoding(sayMethod); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(sayNB)) { NSLog(@"%@ 来了", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return NO; }
这种方式的实现,正好与源码中针对类方法的处理逻辑是一致的,即完美阐述为什么调用了类方法动态方法决议,还要调用对象方法动态方法决议,其根本原因还是类方法在元类中的实例方法
三:消息转发
1:instrumentObjcMessageSends
1.1:在源码里知道了动态方法决议的信息 但是 转发我们再也找不到其相关的线索 所以下面我们使用下面方式来寻找其后续的流程
lookUpImpOrForward
--> log_and_fill_cache
--> logMessageSend
,在其源码下面我们看到了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; }
1.2:通过logMessageSend
源码,了解到消息发送打印信息存储在/tmp/msgSends 目录,如下所示
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.3:开启instrumentObjcMessageSends
2、 在
main
中通过extern
声明instrumentObjcMessageSends
方法3、 打开
objcMsgLogEnabled
开关,即调用instrumentObjcMessageSends
方法时,传入YES4、运行并找到
/tmp/msgSends
目录打开///LGPerson.h @interface LGPerson : NSObject -(void)sayhello; @end ///LGPerson.m #import "LGPerson.h" @implementation LGPerson @end /// main.m extern instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... LGPerson * p = [LGPerson alloc]; instrumentObjcMessageSends(YES); [p sayhello]; instrumentObjcMessageSends(NO); NSLog(@"Hello, World!"); } return 0; }
1.4:这个函数开启之后,会在 /private/tmp
目录下创建一个 msgSends-xxxx
,xxxx
是内部生成的一个编号
1:resolveInstanceMethod
2:forwardingTargetForSelector
3:methodSignatureForSelector
4:forwardInvocation
5:resolveInstanceMethod
6:doesNotRecognizeSelector
1.5:消息转发流程也可以分为两个阶段
-
消息转发的处理主要分为两部分:
- 【快速转发】当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行
快速消息转发
,即走到forwardingTargetForSelector
方法-
如果返回
消息接收者
,在消息接收者中还是没有找到,则进入另一个方法的查找流程 -
如果返回
nil
,则进入慢速消息转发
-
- 【慢速转发】执行到
methodSignatureForSelector
方法-
如果返回的
方法签名
为nil
,则直接崩溃报错
-
如果返回的方法签名
不为nil
,走到forwardInvocation
方法中,对invocation事务进行处理,如果不处理也不会报错
-
- 【快速转发】当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行
2:快速消息转发
我们可以在 forwardingTargetForSelector
方法中将未实现的消息,转发给其他对象。可以在下面的示例代码中看到,返回响应未实现方法的其他对象。
@interface LGPerson : NSObject - (void)sayHello; @end @implementation LGPerson - (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"%@",NSStringFromSelector(aSelector)); return [LGStudent alloc]; } @end
我们可以在 forwardingTargetForSelector
方法中将未实现的消息,转发给其他对象。可以在下面的示例代码中看到,返回响应未实现方法的其他对象。
可以看到,消息接收者成功被转到 ZLPicker
对象身上去了。但是如果 forwardingTargetForSelector
方法未做出任何响应的话,就会来到 消息慢速转发流程上了。
3:慢速转发
3.1:方法签名对象
慢速消息转发时,首先会调用 methodSignatureForSelector
方法,在方法内部生成 NSMethodSignature
类型的方法签名对象。在生成签名对象时,可以指定 target
和 SEL
,可以将这两个参数换成其他参数,将消息转发给其他对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"来了老弟 %s", __FUNCTION__); if (aSelector == @selector(sayBad)) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; }
生成 NSMethodSignature
签名对象后,就会调用 forwardInvocation
方法,这是消息转发中最后一步了。在这一步,只要我们重写了 forwardInvocation
方法,就算不做任何操作,也不会发送消息找不到的崩溃了,只是这样会造成 事务的浪费。
- (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__,anInvocation); anInvocation.target = [LGStudent alloc]; [anInvocation invoke]; }
4:方法决议执行两次
5:总结
如果一个实例方法不能在类和它的继承链的方法列表中不能被找到,则进入到方法解析和消息转发流程。
- 1首先判断当前实例的类对象是否实现了
resolveInstanceMethod
方法,如果实现了,会调用resolveInstanceMethod
方法。这个时候我们可以在resolveInstanceMethod
方法里动态的添加该SEL
对应的方法。之后会 重新执行查找方法实现的流程,如果依旧没找到方法实现,或者没有实现resolveInstanceMethod
方法,则进入消息转发流程。 - 2调用
forwardingTargetForSelector
方法,尝试找到一个能响应该消息的对象。如果找到了,则直接把消息转发给它,如果返回nil,则继续下一步流程。 - 3调用
methodSignatureForSelector
方法,尝试获得一个方法签名,如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常信息。 - 4调用
forwardInvocation
方法,进行事务处理。如果不处理的话,则把事务抛出,爱谁谁接。
注意
1:LGPerson中重写resolveInstanceMethod
方法,并加上class_addMethod
操作即赋值IMP
,此时resolveInstanceMethod
会走两次吗?
【 结论】:通过运行发现,如果赋值了IMP,动态方法决议只会走一次
,说明不是在这里走第二次动态方法决议,
2:forwardingTargetForSelector
resolveInstanceMethod
方法中的赋值IMP,在LGPerson
类中重写forwardingTargetForSelector
方法,并指定返回值为[LGStudent alloc]
,重新运行,如果resolveInstanceMethod
打印了两次,说明是在forwardingTargetForSelector
方法之前执行了 动态方法决议,反之,在forwardingTargetForSelector
方法之后【结论】:发现resolveInstanceMethod
中的打印还是只打印了一次,说明第二次动态方法决议 在forwardingTargetForSelector
方法后
3:在LGPerson中重写 methodSignatureForSelector
和 forwardInvocation
,运行
【结论】:第二次动态方法决议
在 methodSignatureForSelector
和 forwardInvocation
方法之间
引用
1:objc_msgSend 消息发送之 动态方法决议&消息转发