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

动态方法决议解析

作者: 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