当lookupImpOrForward函数从cache和methodTable中找不到对应Method,继续向下执行就会来到resolveMethod_locked函数也就是我们常说的动态方法决议
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
接下来看一下 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);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
里面有 resolveInstanceMethod 和 resolveClassMethod 2个方法,我们来看一下实现。
cls->isMetaClass()的作用是判断cls是否是元类,并且对象的实例方法是存在类中的,而类方法是存在元类中的,因此这里:
如果cls是类,也就是实例方法会调用resolveInstanceMethod方法,
如果cls是元类,类方法则会调用resolveClassMethod方法,
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//首先定义resolveInstanceMethod的方法
SEL resolve_sel = @selector(resolveInstanceMethod:);
//先尝试在类的缓存中查找是否有该resolveInstanceMethod方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
//没有返回
return;
}
//调用类的resolveInstanceMethod方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//objc_msgSend(消息接收者,方法名,参数),相当于在类中调用resolveInstanceMethod方法,返回true代表处理了该方法,否则就有问题。
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
//处理错误信息
}
}
resolveInstanceMethod函数的入参依次为,实例对象、方法名、类对象。
resolveInstanceMethod函数内部会看到objc_msgSend函数,可以看到消息的接受者是cls,所以说resolveInstanceMethod是一个类方法。当系统找不到方法,系统就会调用resolveInstanceMethod
我们在LGPerson类的.m内部实现resolveInstanceMethod函数,并打印,运行:
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s-----%@", __func__ , NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
从打印结果我们可以看到,系统在抛出异常之前会调用resolveInstanceMethod函数,但是会调用2次,它为什么会调用2次呢,我们在文章的最后说。
image.png
我们知道objc_msgSend本质就是通过方法名找具体实现的过程。那我们在resolveInstanceMethod函数内部去给该方法添加一个实现看看。我们首先实现一个method1方法,然后获取该方法的imp,添加到本类里面。
image.png这个样运行时就不会抛出异常了,并且正常调用了method1。
当我们动态的添加了resolveInstanceMethod()方法的时候,系统还是会调用lookUpImpOrNilTryCache()方法,去找IMP。
image.png
这个时候这个imp会不会被缓存呢,就是method1这个方法会不会被缓存,我们验证一下。
通过获取cache里的内容我们找到了test方法,也就是说当方法进行动态决议的时候是会被缓存的.
下面我们来看一下类方法没有实现时怎么处理。
刚才我们看到有一个resolveClassMethod, 我们去调用一下类方法test2
image.png image.png
打印确实看到调用了resolveClassMethod,并走到自己添加的实现。
image.png
再次回到resolveMethod_locked方法中,我们看到元类在调用了resolveClassMethod之后,如果元类中没有imp,那么又再一次调用了resolveInstanceMethod,这是为什么呢?
image.png
我们知道实例方法是存在类里边,而类方法是存放在元类中;那如果类方法找不到的时候,我们应该调用元类的 resolveInstanceMethod。但是元类我们无法修改。根据类与元类的继承关系,就会继续往根元类找,最终找到NSObject的resolveInstanceMethod方法。这一整套调用链路会变得非常长,影响系统运行效率;(如果NSObject没有resolveInstanceMethod方法,我们可以通过写分类进行添加)。因此苹果提供resolveClassMethod方法,其实就是为了简化类方法的查找流程,方便在类方法找不到时,直接通过resolveClassMethod来进行类方法决议,提升调用效率;
image.png当类方法在动态决议时,如果没有找到实现他还会调用resolveInstanceMethod()方法,所以还会调用method1()方法
下面这个例子,在元类中找方法的实现找不到,就要动态决议,会一直找, 变成死循环
image.png
在NSObject分类中写上这个方法,不管是实例方法还是类方法找不到的崩溃就不会出现了,有利于程序的稳定,这么写类似aop(面向切面对象:在不修改源代码的情况下,通过运行时来给程序添加统一的功能,用来进行埋点)
image.png
消息转发
如果系统在动态决议阶段没有找到实现,就会进入消息转发阶段。如果类中没有实现resolveInstanceMethod方法,就会调用methodSignatureForSelector方法,我们就看下消息转发流程。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int 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);
}
省略代码...
}
-------
static void
log_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
cls->cache.insert(sel, imp, receiver);
}
log_and_fill_cache方法中最主要的就是进行方法缓存cache.insert,但这里还有一个logMessageSend进行消息信息打印的过程,那么我们该如何获取这个打印的消息呢?
objcMsgLogEnabled我们看到if判断中有对打印控制的参数,说明可以通过设置这个参数来进行打印日志;全局查找,我们可以找到设置objcMsgLogEnabled的函数,也就是通过instrumentObjcMessageSends函数来设置是否打印日志。
/tmp/msgSends-%d通过查看logMessageSend的源码如下图可以看到,日志输出的路径为/tmp/msgSends-XXX,我们可以在此路径下找到这个日志打印文件。
image.png
image.png
我们调用instrumentObjcMessageSends函数前,需要先声明下该函数,如下:
extern void instrumentObjcMessageSends(BOOL flag);
然后打开该功能
instrumentObjcMessageSends(YES);
运行后,就可以在/tmp目录下找到该文件:
image.png
可以看到在崩溃之前还调用了两个方法forwardingTargetForSelector和methodSignatureForSelector方法。消息发送在经过动态方法解析仍然没有查找到真正的方法实现,此时动态方法决议进入imp = forward_imp消息转发流程。转发流程分两步快速转发和慢速转发。
消息的快速转发
我们在LGPerson类中声明test3方法,不实现,然后定义一个LGBoy类, 在LGBoy类中实现test1,然后在LGPerson类中实现forwardingTargetForSelector:方法,将LGPerson的test3方法转发到LGBoy类中,也就是说,快速转发后的类必须有同名的方法。如下代码:
image.png
image.png
转发的作用在于,如果当前对象无法响应消息,就将它转发给能响应的对象。并且方法缓存在接收转发消息的对象的cache中
消息的慢速转发
在快速转发过程中,如果我们不做处理,此时就会进入到methodSignatureForSelector方法, 也就是慢速转发。
image.png
网友评论