美文网首页
动态方法决议解析

动态方法决议解析

作者: iOSer_jia | 来源:发表于2020-10-02 23:45 被阅读0次

当一个方法经过快速查找、慢速查找都没有找到时,系统并不会立马报错,他会给开发者一个“补救“的机会,那便是动态方法决议。

上一篇文章里介绍到,如果在类的方法列表里没有找到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

实现resolveInstanceMethodresolveClassMethod时需要调用class_addMethod往类或元类中添加imp,因为动态方法决议会再次调用lookUpImpOrNil在类或元类中查找。

相关文章

网友评论

      本文标题:动态方法决议解析

      本文链接:https://www.haomeiwen.com/subject/wkbiuktx.html