当一个方法经过快速查找、慢速查找都没有找到时,系统并不会立马报错,他会给开发者一个“补救“的机会,那便是动态方法决议。
在上一篇文章里介绍到,如果在类的方法列表里没有找到imp,查找方法流程工会走到resolveMethod_locked
这里,本文将同过这个房探究动态方法决议的流程。
resolveInstanceMethod
查看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 (!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);
}
可以看到对与对像方法和类方法,动态方法决议有着不同的处理方法,对象方法会走到resolveInstanceMethod
这个函数。
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 查找类中有无名为resolveInstanceMethod的方法
// 如果没有实现,直接返回,如果有,继续流程
// lookUpImpOrNil内部实际调用了lookUpImpOrForward,所以也会去父类查找resolveInstanceMethod方法
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
// 调用resolveInstanceMethod方法
// 此时我们应该在resolveInstanceMethod这个方法里动态添加imp
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
// 重新查找方法列表
// 如果开发者有在resolveInstanceMethod这个方法中添加目标方法的imp,那么此时是可以找到方法实现的
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));
}
}
}
多说无益,我们直接通过代码演示。
如果对象调用一个没有实现的方法,结果显然会报找不到方法的错误。
@interface Animal : NSObject
- (void)eat;
@end
@implementation Animal
@end
Animal *an = [Animal alloc];
[an eat];
这段代码运行时候肯定会崩溃,但我们可以通过动态方法决议补救这次崩溃。
@implementation Animal
void eat() {
NSLog(@"eat");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
IMP imp = eat;
const char *type = "v:";
class_addMethod(self, @selector(eat), imp, type);
return NO;
}
@end
此时程序正常运行,没有崩溃,这便是动态方法决议带来的意义。
类方法的动态方法决议
与对象方法的动态方法决议略有不同,类方法的动态方法决议的过程如下:
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
关于resolveClassMethod
的定义,它实际上是与resolveInstanceMethod
类似的流程,只不过它调用的方法是resolveClassMethod
。
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
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));
}
}
}
而类方法的动态方法决议会在类没有实现resolveClassMethod
的情况下调用resolveInstanceMethod
,我们可以用代码做验证。
@interface NSObject (Category)
@end
#import "NSObject+Category.h"
#import <objc/message.h>
@implementation NSObject (Category)
void run() {
NSLog(@"run");
}
void eat() {
NSLog(@"eat");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
const char *type = "v:";
if (sel == @selector(eat)) {
IMP imp = eat;
class_addMethod(self, sel, imp, type);
} else if (sel == @selector(run)) {
IMP imp = run;
class_addMethod(self, @selector(run), imp, type);
}
return NO;
}
@end
从isa走位图我们知道,类对象的superclass指针最终会指向根类NSObject,而元类的superclass也最终会回到NSObject类对象,所以我们可以统一在NSObject中做处理。
此时调用
Animal *an = [Animal alloc];
[an eat];
[Animal run];
执行结果
2020-10-02 22:20:47.741185+0800 动态决议[86048:11764807] eat
2020-10-02 22:20:47.741494+0800 动态决议[86048:11764807] run
对此,我们可以在NSObject的分类中实现动态方法决议类解决方法调用失败导致的崩溃问题,统一在resolveInstanceMethod
这个方法处理,比如异常信息上报,避免程序闪退等。
虽然在这个方法可以解决类找不到imp导致崩溃的痛点,但是这种处理方式显得过于霸道,如果在团队可开发中另一个开发者并不知道已经实现了这个方法,而是在后续的快速转发或慢速转发中处理了,那么他是无法去到他所想的流程的,因为我们动态方法决议中已经处理了。
总结
动态方法决议可以用下图表示:
动态方法决议.png实现
resolveInstanceMethod
或resolveClassMethod
时需要调用class_addMethod
往类或元类中添加imp,因为动态方法决议会再次调用lookUpImpOrNil
在类或元类中查找。
网友评论