前言
前面已经分析了objc_msgSend快速查找流程和objc_msgSend慢速查找流程,本文就来探索动态方法决议。
准备工作
- objc4-818.2源码。
1: 动态方法决议相关源码解析
前文objc_msgSend慢速查找流程里已经分析过了,在慢速查找过程中,如果父类返回的是消息转发forward_imp
或者所有父类都查找完之后也没有查找到对应的imp
,就会执行一次动态方法决议。
// 篇幅原因,仅截取部分代码
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
...
// No implementation found. Try method resolver once.
// 1.父类缓存返回forward_imp,先不缓存执行一次动态方法决议
// 2.所有父类都查找完了,也没查找到对应的imp,执行消息转发之前,执行一次动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
...
}
- 父类缓存快速查找返回
forward_imp
,先不缓存执行一次动态方法决议。 - 所有父类都查找完了(父类已经为
nil
了),也没查找到对应的imp
,执行消息转发之前,执行一次动态方法决议。
1.1: 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()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else { // 如果是元类,说明调用的是类方法
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
// 如果+resolveClassMethod:方法没有实现就找resolveInstanceMethod:方法
// NSObject默认实现了这两个方法
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 动态方法决议可能已经填充了缓存(可能为动态方法决议添加的`imp`或者消息转发`forward_imp`),所以尝试使用它
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 判断
cls
是否是元类元类
。 - 不是元类就执行
resolveInstanceMethod
流程。 - 是元类就先执行
resolveClassMethod
流程。 - 动态方法决议可能已经填充了缓存(可能为动态方法决议添加的
imp
或者消息转发forward_imp
),所以尝试使用它。
1.2: resolveInstanceMethod
源码解析
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
// inst 对象 cls 类
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// NSObject默认实现了+resolveInstanceMethod:方法
// 在cls的元类查找+resolveInstanceMethod:方法,先快速,再慢速
// 目的是将+resolveInstanceMethod:方法缓存到cls的元类中
// 通过lookUpImpOrNilTryCache的参数我们知道`resolveInstanceMethod:`是类方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 发送消息调用resolveInstanceMethod:方法
// 如果动态决议给当前sel添加了imp,就会存入类的方法列表
// 通过objc_msgSend发送消息,receiver是cls,说明是类方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
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到类中
// 如果动态决议没处理就缓存forward_imp
// 动态方法决议下次不会再触发
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) { // imp存在,说明动态添加了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));
}
}
}
- 以
resolveInstanceMethod:
方法创建sel
。 - 调用
lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))
缓存resolveInstanceMethod:
方法到cls
的元类中。 - 对
objc_msgSend
进行类型转换,然后给类发消息调用resolveInstanceMethod:
方法,如果动态决议给当前sel
添加了imp
,就会存入类的方法列表。 - 调用
lookUpImpOrNilTryCache(inst, sel, cls)
,看缓存中是否有sel
对应的imp
,如果没有,就调用lookUpImpOrForward
,动态方法决议处理了的话,就缓存动态方法决议添加的imp
,动态方法决议没处理的话,就缓存消息转发的forward_imp
,动态方法决议下次不会再触发。
1.3: resolveClassMethod
源码解析
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
// inst 类 cls 元类
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// NSObject默认实现了+resolveClassMethod:方法
// 在元类cls查找+resolveClassMethod:方法,先快速,再慢速
// 目的是将+resolveClassMethod:方法缓存到元类cls中
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);
}
}
// 发送消息调用resolveClassMethod:方法
// 如果动态决议给当前sel添加了imp,就会存入元类的方法列表
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到元类中
// 如果动态决议没处理就缓存forward_imp
// 动态方法决议下次不会再触发
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) { // imp存在,说明动态添加了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));
}
}
}
- 调用
lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)
缓存resolveInstanceMethod:
方法到元类cls
中。 - 对
objc_msgSend
进行类型转换,然后给类发消息调用resolveClassMethod:
方法,如果动态决议给当前sel
添加了imp
,就会存入元类的方法列表。 - 调用
lookUpImpOrNilTryCache(inst, sel, cls)
,看缓存中是否有sel
对应的imp
,如果没有,就调用lookUpImpOrForward
,动态方法决议处理了的话,就缓存动态方法决议添加的imp
,动态方法决议没处理的话,就缓存消息转发的forward_imp
,动态方法决议下次不会再触发。
1.4: lookUpImpOrNilTryCache
源码解析
在resolveInstanceMethod
函数和resolveClassMethod
函数中都会调用此函数。
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
- 调用
_lookUpImpTryCache
根据sel
查找imp
,behavior
默认为0
,LOOKUP_NIL = 4
,behavior | LOOKUP_NIL
大于等于LOOKUP_NIL
。
1.5: _lookUpImpTryCache
源码解析
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
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)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
- 判断
cls
是否初始化,一般都已经初始化了。
快速查找流程
- 根据
sel
到cls
缓存中查找对应的imp
。 - 如果
imp
存在就跳转done
流程,``。 - 不存在就先判断是否支持共享缓存,支持就到共享缓存中查找对应的
imp
。 - 如果缓存中没有查找到对应的
imp
,就进入慢速查找流程。
慢速查找流程
- 慢速查找流程中,
behavior = 4,LOOKUP_RESOLVER = 2
,4 & 2 = 0
,不会再次进入动态方法决议,所以不会形成死循环。 - 慢速流程如果没有查找到,
imp
会被赋值为消息转发forward_imp
,然后插入缓存,如果(behavior & LOOKUP_NIL) && imp == forward_imp)
,返回nil
,否则返回forward_imp
,进入消息转发流程(后续单独发文解析)。
done
流程
- 如果
(behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache
,就直接返回nil
,否则返回imp
,此判断条件说明了imp不为空一定是动态方法决议里面给sel
动态添加了imp
。
1.6: lookUpImpOrForwardTryCache
源码解析
extern IMP lookUpImpOrForwardTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
- 调用
_lookUpImpTryCache
根据sel
查找imp
,behavior
默认为0
,此处为1
,所以不会触发LOOKUP_NIL
返回nil
,而是返回动态方法决议添加的imp
或是消息转发forward_imp
。
2: resolveInstanceMethod
案例分析
2.1: 二次调用分析
还是前文objc_msgSend慢速查找流程的单身狗没有女朋友案例,创建SDSingleDog
类,声明- (void)girlfriend
方法,不实现,运行代码,单身狗开始找女朋友,没找到,他就崩溃了(充分说明了没有女朋友是一件很让人崩溃的事)。
@interface SDSingleDog : NSObject
- (void)girlfriend;
@end
@implementation SDSingleDog
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"__对象方法动态决议来了%s__", __func__);
return [super resolveInstanceMethod:sel];
}
@end
********************* 打印输出 *********************
2021-07-11 02:25:15.231564+0800 MsgResolve[11978:289761] __对象方法动态决议来了+[SDSingleDog resolveInstanceMethod:]__
2021-07-11 02:25:15.232133+0800 MsgResolve[11978:289761] __对象方法动态决议来了+[SDSingleDog resolveInstanceMethod:]__
2021-07-11 02:25:15.232250+0800 MsgResolve[11978:289761] -[SDSingleDog girlfriend]: unrecognized selector sent to instance 0x10049d700
在崩溃之前确实调用了+resolveInstanceMethod:
方法,但是却调用了两次,第一次是方法慢速查找流程中触发的,那么第二次是怎么触发的呢?
第一次调用+resolveInstanceMethod:
方法的堆栈信息,可以看到是方法慢速查找流程触发的动态方法决议。
第二次调用+resolveInstanceMethod:
方法的堆栈信息,可以看到是由CoreFoundation
库在消息转发流程完成以后再次开启方法慢速查找流程触发的方法动态决议,消息转发流程后续单独发文分析。
2.2: 动态添加对象方法
为了让单身狗不崩溃,我们给他加上一个备胎的方法,给他点希望。
@implementation SDSingleDog
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"__对象方法动态决议来了%s__", __func__);
if (sel == @selector(girlfriend)) {
IMP imp = class_getMethodImplementation(self, @selector(spareTire));
Method meth = class_getInstanceMethod(self, @selector(spareTire));
const char *type = method_getTypeEncoding(meth);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
- (void)spareTire
{
NSLog(@"The single dog becomes spare tire");
}
@end
********************* 打印输出 *********************
2021-07-11 03:27:35.190669+0800 MsgResolve[13132:322186] __对象方法动态决议来了+[SDSingleDog resolveInstanceMethod:]__
2021-07-11 03:27:35.191228+0800 MsgResolve[13132:322186] ____The single dog becomes spare tire____
+resolveInstanceMethod:
方法只调用了一次,因为resolveInstanceMethod
函数将动态方法决议添加的imp
缓存到了类中,lookUpImpOrForwardTryCache
函数获取到了imp
,直接调用imp
,查找流程结束。- 崩溃也解决了,实现动态方法决议,系统给了我们一次机会。
- 具体流程:
resolveMethod_locked
->resolveInstanceMethod
->lookUpImpOrNilTryCache
->+resolveInstanceMethod:
->lookUpImpOrNilTryCache
->lookUpImpOrForwardTryCache
->调用imp
。
3: resolveClassMethod
案例分析
3.1: 二次调用分析
给SDSingleDog
类声明一个+ (void)getMarried
方法,不实现,运行代码,单身狗想结婚,很显然,他又崩溃了。
@interface SDSingleDog : NSObject
+ (void)getMarried;
@end
@implementation SDSingleDog
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"__类方法动态决议来了%s__", __func__);
return [super resolveClassMethod:sel];
}
@end
********************* 打印输出 *********************
2021-07-11 16:35:53.973393+0800 MsgResolve[1441:24199] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 16:35:53.974148+0800 MsgResolve[1441:24199] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 16:35:53.974246+0800 MsgResolve[1441:24199] +[SDSingleDog getMarried]: unrecognized selector sent to class 0x100008188
-
在崩溃之前确实调用了
+resolveClassMethod:
方法,而且调用了两次,调用两次的逻辑和+resolveInstanceMethod:
方法是一致的。 -
调用
resolveClassMethod
函数后,会调用lookUpImpOrNilTryCache
函数去查找有没有为sel
动态添加的imp
,元类缓存中此时sel
对应的imp
是forward_imp
,lookUpImpOrNilTryCache
里面的判断直接返回nil
,此时再调用resolveInstanceMethod
函数查找,因为根元类的父类也是根类。 -
如果
+resolveClassMethod:
方法和+resolveInstanceMethod:
方法都没有为当前sel
动态添加imp
,调用lookUpImpOrForwardTryCache
函数就会返回消息转发forward_imp
,进入消息转发流程。
3.2: 动态添加类方法
为了让单身狗不崩溃,我们给他加上一个相亲的方法,单身狗也有很多很优秀的,他们只是太忙了,没时间找女朋友,通过相亲,希望他们可以获得美好的婚姻。
@implementation SDSingleDog
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"__类方法动态决议来了%s__", __func__);
if (sel == @selector(getMarried)) {
IMP imp = class_getMethodImplementation(self, @selector(blindDate));
Method meth = class_getClassMethod(self, @selector(blindDate));
const char *type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass(self), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
+ (void)blindDate
{
NSLog(@"____The single dog began to blind date____");
}
@end
********************* 打印输出 *********************
2021-07-11 17:29:31.416153+0800 MsgResolve[2387:51634] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 17:29:31.416787+0800 MsgResolve[2387:51634] ____The single dog began to blind date____
+resolveClassMethod:
方法只调用了一次,因为resolveClassMethod
函数将动态方法决议添加的imp
缓存到了类中,lookUpImpOrForwardTryCache
函数获取到了imp
,直接调用imp
,查找流程结束。- 崩溃也解决了,实现动态方法决议,系统给了我们一次机会。
resolveClassMethod
和resolveInstanceMethod
流程大致相同,只是如果+resolveClassMethod:
没有为当前sel
动态添加imp
就会调用一次resolveInstanceMethod
。
3.3: resolveClassMethod
流程分析
源码逻辑显示调用resolveClassMethod
函数没有查找到动态添加的imp
,就会再调用一次resolveInstanceMethod
,那么在SDSingleDog
类中同时实现+ (BOOL)resolveInstanceMethod:(SEL)sel
方法和+ (BOOL)resolveClassMethod:(SEL)sel
方法,理论上应该都会调用。
@implementation SDSingleDog
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"__对象方法动态决议来了%s__", __func__);
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"__类方法动态决议来了%s__", __func__);
return [super resolveClassMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SDSingleDog getMarried];
}
return 0;
}
********************* 打印输出 *********************
2021-07-11 18:11:26.621227+0800 KCObjcBuild[3722:76122] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 18:11:26.621915+0800 KCObjcBuild[3722:76122] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 18:11:26.622111+0800 KCObjcBuild[3722:76122] +[SDSingleDog getMarried]: unrecognized selector sent to class 0x1000082b0
输出结果出乎我们的意料,只是调用了+resolveClassMethod:
方法,下面就跟着流程来分析一下。
inst
为SDSingleDog
类,cls
为SDSingleDog
元类,如果+resolveClassMethod:
方法没有动态为当前sel
动态添加imp
,就会调用resolveInstanceMethod
函数。
-
调用
lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))
将+resolveInstanceMethod:
方法缓存到元类cls(SDSingleDog(metaclass))
的isa
指向的根元类NSObject(metaclass)
中。 -
给元类
cls(SDSingleDog(metaclass))
发送+resolveInstanceMethod:
的消息,首先到根元类NSObject(metaClass)
中查找,但是系统默认实现的在根类NSObject(class)
中,只是返回了NO
,我们实现的在类SDSingleDog(class)
中(方法存在元类SDSingleDog(metaclass)
),所以不会走SDSingleDog(class)
类中实现的+resolveInstanceMethod:
方法。 -
调用
lookUpImpOrNilTryCache
函数->_lookUpImpTryCache
函数,默认参数behavior | LOOKUP_NIL
,将+resolveInstanceMethod:
方法动态添加的imp
或消息转发的forward_imp
缓存到元类SDSingleDog(metaclass)
中(此处因为根本没有调用到+resolveInstanceMethod:
方法,所以缓存的是消息转发的forward_imp
)。
由上面的分析推测如果在根类NSObject
的分类中添加+resolveInstanceMethod:
方法,在+resolveClassMethod:
方法调用之后,因为没有为当前sel
动态添加imp
,所以也会调用到根类NSObject
分类的+resolveInstanceMethod:
方法。
@implementation NSObject (Goddess)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(getMarried)) {
NSLog(@"__对象方法动态决议来了%s__", __func__);
}
return NO;
}
@end
********************* 打印输出 *********************
2021-07-11 23:59:45.932837+0800 KCObjcBuild[10139:220823] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 23:59:45.933468+0800 KCObjcBuild[10139:220823] __对象方法动态决议来了+[NSObject(Goddess) resolveInstanceMethod:]__
2021-07-11 23:59:45.933814+0800 KCObjcBuild[10139:220823] __类方法动态决议来了+[SDSingleDog resolveClassMethod:]__
2021-07-11 23:59:45.933883+0800 KCObjcBuild[10139:220823] __对象方法动态决议来了+[NSObject(Goddess) resolveInstanceMethod:]__
2021-07-11 23:59:45.933990+0800 KCObjcBuild[10139:220823] +[SDSingleDog getMarried]: unrecognized selector sent to class 0x1000082d0
跟推测的一样,先调用+resolveClassMethod:
方法,再调用+resolveInstanceMethod:
方法,由于没有动态添加imp
,所以都是两次。
4: 动态方法决议整合
经过上面分析,+resolveClassMethod:
方法中没有为当前sel
动态添加imp
的话,也会调用+resolveInstanceMethod:
方法,那么就可以建立一个公共类,实现+resolveInstanceMethod:
方法,使类方法
和对象方法
都能调用。
已知方法查找流程
- 对象方法查找流程:
类
->父类
->...->根类(NSObject)
->nil
。 - 类方法查找流程:
元类
->父元类
->...->根类(NSObject)
->nil
。
由方法查找流程可知,所有方法最后都会查找到根类(NSObject)
中,所以我们就可以定义一个NSObject
的分类来充当动态方法决议的公共类。
@implementation NSObject (Goddess)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(getMarried)) {
NSLog(@"__类方法动态决议来了%s__", __func__);
IMP imp = class_getMethodImplementation(object_getClass(self), @selector(blindDate));
Method meth = class_getClassMethod(object_getClass(self), @selector(blindDate));
const char *type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass(self), sel, imp, type);
} else if (sel == @selector(girlfriend)) {
NSLog(@"__对象方法动态决议来了%s__", __func__);
IMP imp = class_getMethodImplementation(self, @selector(spareTire));
Method meth = class_getInstanceMethod(self, @selector(spareTire));
const char *type = method_getTypeEncoding(meth);
return class_addMethod(self, sel, imp, type);
}
return NO;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SDSingleDog *singleDog = [SDSingleDog alloc];
[singleDog girlfriend];
[SDSingleDog getMarried];
}
return 0;
}
********************* 打印输出 *********************
2021-07-12 00:26:38.410687+0800 KCObjcBuild[10752:236964] __对象方法动态决议来了+[NSObject(Goddess) resolveInstanceMethod:]__
2021-07-12 00:26:38.411218+0800 KCObjcBuild[10752:236964] ____The single dog becomes spare tire____
2021-07-12 00:26:38.411350+0800 KCObjcBuild[10752:236964] __类方法动态决议来了+[NSObject(Goddess) resolveInstanceMethod:]__
2021-07-12 00:26:38.411517+0800 KCObjcBuild[10752:236964] ____The single dog began to blind date____
@end
对象方法和类方法的动态决议最后调用到了+resolveInstanceMethod:
方法中,和上面分析的结果吻合。
动态方法决议的优点:
- 统一处理方法找不到的崩溃问题,APP出现方法崩溃时可以上报服务器或者跳转到首页,增加APP的健壮性。
这种编程方式叫面向切面编程,即AOP
。
AOP
和OOP
的区别
OOP
(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。AOP
(面向切面编程)是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP提取出来的功能移除也不会对主代码造成影响。AOP更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP串联起来,本身平面内的各个类没有任何的关联。
5: 动态方法决议流程图
6: 日志辅助调试
方法快速查找和慢速查找都没有查找到对应的imp
,动态方法决议也没有动态添加对应的imp
,就会进入消息转发的流程,但是从上面分析可知消息转发流程是CoreFunction
实现的,而CoreFunction
提供的源码既不全面也没有对应的工程,添加到现有工程搜索_CF_forwarding_prep_0
函数、___forwarding___
函数和doesNotRecognizeSelector
方法都无法找到。这种情况下就需要使用苹果的日志辅助功能了(arm64
架构不支持此方法)。
通过lookUpImpOrForward
->log_and_fill_cache
->logMessageSend
查看源码。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
// objcMsgLogEnabled为YES才能调用logMessageSend
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
objcMsgLogEnabled
为YES
才能调用logMessageSend
函数。
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;
}
日志文件的沙盒路径是/tmp/msgSends
,写入后直接到沙盒路径下就可以获取到,objcMsgLogEnabled
默认为false
,所以需要找到赋值的地方。
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;
}
调用instrumentObjcMessageSends
函数可以为objcMsgLogEnabled
赋值,所以在需要日志信息的地方公开声明下instrumentObjcMessageSends
,既extern void instrumentObjcMessageSends(BOOL flag)
,就可以调用此函数控制日志写入了。
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
SDSingleDog *singleDog = [SDSingleDog alloc];
// 在我们需要记录日志的方法前开启
instrumentObjcMessageSends(YES);
[singleDog girlfriend];
// 在我们需要记录日志的方法后关闭,防止多余信息混淆我们
instrumentObjcMessageSends(NO);
}
return 0;
}
根据日志文件我们发现在动态方法决议之后的消息转发流程有forwardingTargetForSelector
和methodSignatureForSelector
两个方法,后续将单独发文分析消息转发流程。
7: 总结
动态方法决议是苹果为了程序的健壮性而设计的一种容错处理机制,在相关方法没有实现时,给开发者提供动态实现的机会,同时也为开发者提供了更多新的尝试的可能性。