前言
上一篇文章慢速方法查找一文详细分析了消息慢速查找的流程,当在找不到的时候imp = forward_imp(消息转发),那么这篇文章主要就是探索消息转发的过程,以及我们可以在这过程中可以做出哪些灵性的处理。动态方法决议又是怎么实现的?带着问题开始我们的探索吧!!哈哈
动态方法决议
通过汇编的断点可以得知,当imp没有找到的时候会进入libobjc.A.dylib_objc_msgForward_impcache方法,那么上篇文章_lookUpImpOrForward慢速方法查找已经知道其中的逻辑如下:
//动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//behavior = 3 LOOKUP_RESOLVER = 2
//3^2 = 1
behavior ^= LOOKUP_RESOLVER;
//动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
-
behavior上篇文章已经分析,值中有LOOKUP_INITIALIZE|LOOKUP_RESOLVER进入后异或LOOKUP_INITIALIZE|LOOKUP_RESOLVER^ LOOKUP_RESOLVER = LOOKUP_INITIALIZE,相当于清空了LOOKUP_RESOLVER。 -
resolveMethod_locked的最后一个参数就是LOOKUP_INITIALIZE。
resolveMethod_locked源码分析
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//cls不是元类进入以下判断
if (! cls->isMetaClass()) {
resolveInstanceMethod(inst, sel, cls);
}
else {//这里是cls是元类
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);
}
- 当快速查找和慢速查找都没有找到需要的
imp的时候,就会进入动态方法决议查找的流程,即resolveMethod_locked方法。 - 查找的是实例方法则进行对象方法动态决议
resolveInstanceMethod。 - 查找的流程会根据
isa的走位来进行查找,类->元类->根元类。 - 如果都没找到,最后会调用
lookUpImpOrForwardTryCache查找(重新查找一遍)。
补充:查找的是类方法则先进行类方法动态决议resolveClassMethod,再执行resolveInstanceMethod(这里resolveInstanceMethod调用与实例方法的resolveInstanceMethod参数不同。)。
lookUpImpOrNilTryCache源码分析
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//判断类是否已经初始化,正常情况下类是已经初始化了,所以这个判断基本不会进入
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
//进入慢速消息查找流程,因为已经查找动态决议方法,之后behavior = LOOK_INITIALIZE
//没有了动态决议的参数(LOOK_RESOLVER)
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//进行缓存快速查找
IMP imp = cache_getImp(cls, sel);
//缓存中查找到imp,直接进行done流程
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
//动态共享缓存中查找
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
//imp不存在的话,继续进行消息的慢速查找流程
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//判断消息是否已经转发,_objc_msgForward_impcache方法会讲方法写进缓存
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
//返回imp,在之后的流程处理
return imp;
}
-
isInitialized用来判断类(cls)是否已经初始化,一般情况下是不会进入的。 - 先去查找缓存
(cache)中的imp,有的话就返回imp。 - 没有在缓存
(cache)中找到的话,就会尝试在共享缓存中查找,找到就返回imp。 - 仍然没有会进行
lookUpImpOrForward也就是再进行一次慢速消息查找。 -
lookUpImpOrNilTryCache的主要作用通过LOOKUP_NIL来控制插入缓存,不管sel对应的imp有没有实现,还有就是如果imp返回了有值那么一定是在动态方法决议中动态实现了imp。
注意:既然这个函数也是进行快速和慢速消息查找的,那么就说明resolveInstanceMethod与resolveClassMethod可以在某个时机将方法加入类中(加入到cache)。这样后面方法的调用才有意义。
对象方法动态决议 resolveInstanceMethod
通过以上的分析,就是在快速和慢速消息查找过程中找不到imp的话,苹果仍然会给机会给我们在resolveInstanceMethod方法中进行处理,那么我们可以猜想在resolveInstanceMethod会在类中添加imp,请往下看resolveInstanceMethod方法源码:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//先进行元类查找,是否实现了resolveInstanceMethod实例方法,也就是类方法。
//没有实现的话就返回,但是这里不返回,原因是NSObject默认实现了resolveInstanceMethod
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//发送消息(resolveInstanceMethod),由于接受者是类,所以以下判断进去"+"方法
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 imp = lookUpImpOrNilTryCache(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));
}
}
}
- 首先判断
cls是否为元类。 - 先进行元类
resolveInstanceMethod方法的查找,找到之后会进行缓存。 - 系统会自动给类(
cls)发送resolveInstanceMethod消息,既然是给类发送消息,那么resolveInstanceMethod是类方法。(+resolveInstanceMethod)。 - 接着进行
imp的快速和慢速的查找流程,但是resolveInstanceMethod方法没有返回imp,原因在于这里不需要返回只需要对缓存进行更新的处理。 -
lookUpImpOrNilTryCache与lookUpImpOrForwardTryCache唯一区别就是是否进行动态转发,这里是不进行。 - 可以看到返回的
resolved只是进行了日志打印。也就是resolved返回YES/NO对功能没有影响。
注意:如果没有实现,缓存中没有,进入lookUpImpOrForward查找,sel没有查找到对应的imp,此时imp = forward_imp,动态方法决议只调用一次,此时会走done_unlock和done流程,既sel和forward_imp插入缓存,进行消息转发。
类方法resolveInstanceMethod动态决议
static void resolveClassMethod(id inst, SEL sel, Class cls)
{ //inst->对象 cls->类 sel->方法编号
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//查询元类是否实现,NSObject默认实现了resolveClassMethod
if (!lookUpImpOrNilTryCache(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 = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
//以下是进行一些日志的输出
if (imp) {
...
}
else {
// Method resolver didn't add anything?
...
}
}
}
-
resolveClassMethod在NSobject中已经实现,只要元类初始化就可以了,目的是缓存在元类中。 - 调用
resolveClassMethod类方法,目的是实现可能resolveClassMethod方法中动态实现sel对应的imp。 -
imp = lookUpImpOrNilTryCache(inst, sel, cls)缓存sel对应的imp,不管imp有没有动态添加,如果没有缓存的就是forward_imp。
resolveInstanceMethod实例探究
创建XXPerson类,声明sayLost方法,但是不进行实现,代码如下:
@interface XXPerson : NSObject
-(void)sayLost;
@end
在XXperson.m中添加resolveInstanceMethod方法,并打印相关的信息,代码如下:
#import "XXPerson.h"
@implementation XXPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"--xjl--%@",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
进行sayLost调用之后发现如下:

疑问:在崩溃之前确实调用了
resolveInstanceMethod方法,而且调用了2次,这是为什么呢?众所周知,第一次系统会走动态方法决议,NSObject调用resolveInstanceMethod,那么第二次呢?
打开函数调用栈查看情况如下:(注意栈是先进后出的)

- 第一次进入
resolveInstanceMethod时查看堆栈信息,发现走的是慢速查找流程的动态决议方法。

- 由上图可知,第二次调用
resolveInstanceMethod是由系统库coreFoundation调起的。在消息转发完成之后再次开启了慢速查找流程,进入动态方法决议又调用了一次resolveInstanceMethod,所以总共调用了两次,第二次调用的详细流程会在后面详细分析。
动态添加sayLost方法

- 当第一次进来
resolveInstanceMethod方法的时候,我们动态添加了sayLost方法,lookUpImpOrForwardTryCache直接获取imp,直接调用imp,查找流程结束。 - 动态方法协议成功之后程序
崩溃情况也得到了解决,这是系统给开发者容错的机会。
具体流程:resolveMethod_locked--> resolveInstanceMethod --> 调用resolveInstanceMethod --> lookUpImpOrNilTryCache(inst, sel, cls) --> lookUpImpOrForwardTryCache --> 调用imp。
resolveClassMethod实例探究
新创建XJLPerson类,并在类中定义类方法+(void)test,在XJLPerson.m文件中实现+resolveClassMethod方法,代码如下:
@interface XJLPerson : NSObject
+(void)test;
@end
#import "XJLPerson.h"
@implementation XJLPerson
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"--xjl--%@",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
@end
调用结果:
2021-07-13 16:18:02.437689+0800 KCObjcBuild[16765:324532] --xjl--test
2021-07-13 16:18:02.438672+0800 KCObjcBuild[16765:324532] --xjl--test
2021-07-13 16:18:02.439058+0800 KCObjcBuild[16765:324532] +[XJLPerson test]: unrecognized selector sent to class 0x1000086b0
-
resolveClassMethod方法也像resolveInstanceMethod方法一样,调用了两次,而且逻辑都是一样的。 - 调用
resolveClassMethod以后,会去查找lookUpImpOrNilTryCache有没有具体动态实现sel对应的imp,元类的缓存中此时有sel对应的imp,这个imp是forward_imp。lookUpImpOrNilTryCache里面有判断直接返回nil,此时直接到resolveInstanceMethod查找,因为类方法实际上就是元类中的实例方。 - 如果最后还是没有实现
lookUpImpOrForwardTryCache获取到forward_imp进入消息转发流程。
动态添加+test方法

-
resolveClassMethod只调用一次,因为动态添加了test方法 -
resolveClassMethod和resolveInstanceMethod的调用流程基本一样,如果resolveClassMethod没有查询到调用一次resolveInstanceMethod调用。
resolveClassMethod方法拓展
@interface XJLPerson : NSObject
+(void)test;
@end
#import "XJLPerson.h"
@implementation XJLPerson
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"resolveClassMethod--xjl--%@",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod--xjl--%@",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
@end
打印结果如下:
2021-07-13 17:04:29.162523+0800 KCObjcBuild[17860:344756] resolveClassMethod--xjl--test
2021-07-13 17:04:29.163433+0800 KCObjcBuild[17860:344756] resolveClassMethod--xjl--test
这就奇怪了,进入了两次resolveClassMethod方法,完全没有进入resolveInstanceMethod方法啊。不是说了类方法就是元类的实例方法嘛?别急,我们先进去源码断点看看是啥回事咯!!!别慌!

通过上面
lldb调试发现inst是XJLPerson类,其sa指向元类与cls的地址一致,那么cls就是XJLPerson的元类。断点进入
resolveClassMethod方法中调用的lookUpImpOrNilTryCache方法:
此时
inst是XJLPerson的元类,cls是根元类。快速和慢速查找实到根元类查找,意味着元类调用了实例方法。
msg(cls,resolve_sel,sel)也可以验证objc_msgSend是发送消息是不区分-和+方法的。objc_msgSend的接收者cls是元类,这意味着向元类中发消息,消息的查找会到元类的元类(根元类)中查找,所以resolveInstanceMethod在元类中,在类中是不被调用的。虽然类和元类的名字一样,但是地址是不一样的。这就解释了为什么XJLPerson类中的resolveInstanceMethod没被调用。
按照以上分析的逻辑,根元类的父类是NSObject(根类),如果根元类中找不到方法的时候,会在根类中查找,那么我们创建NSObject+XJL的分类,里面实现+resolveInstanceMethod,代码如下:
#import "NSObject+XJL.h"
@implementation NSObject (XJL)
+(BOOL)resolveInstanceMethod:(SEL)sel{
if(@selector(test) == sel){
NSLog(@"resolveInstanceMethod--进入%@--",NSStringFromSelector(sel));
}
return NO;
}
@end
打印结果:

根代码逻辑是一样的,先会调用
resolveClassMethod方法,再调用resolveInstanceMethod方法,而且都会调用2次。
动态方法决议的具体运用
resolveClassMethod方法中如果没有动态添加类方法,会调用元类中的resolveInstanceMethod。猜想能不能把resolveInstanceMethod写到一个公用类中,使类方法和实例方法都能调用。
- 实例方法查找流程:
对象-->类-->直到根类(NSObject)-->nil。 - 类方法查找流程:
类-->元类-->直到根类(NSObject)-->nil。
最后还是无论是类方法还是实例方法都会走到根类(NSObject)中查找方法,那么我们创建一个NSObject+XJL的分类,来提供动态方法,代码如下:

-
实例方法跟类方法都调用了resolveInstanceMethod方法,区别在于开始训着方法的地方实例方法是类中,类方法在元类中。
动态方法决议优点
- 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器,或者跳转到首页又或许做其他的操作。
- 如果项目中是不同的模块你可以根据命名不同,进行业务的区别。
- 这种方式叫
切面编程---AOP。
拓展一下AOP和OOP
-
OOP:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合。 -
AOP:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP提取出来的功能移除也不会对主代码造成影响。AOP更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP串联起来,本身平面内的各个类没有任何的关联。
注意:如果需要详细了解AOP的小伙伴可以自行查找一些资料,我这里就引出一下知识点而已哦。
总结
学习完动态方法决议后,其实发觉它的主要意义是给多一次机会我们去处理一些崩溃的方法,这样子能够大大提高APP的流畅性和容错性。但是这篇文章说讲的动态方法决议只是引出其使用的场景,这样子操作其实也不是非常的合理的,下一篇文章会在消息的转发里程中得到解决哦,敬请期待!!加油!,










