美文网首页
10.iOS底层原理之动态方法决议

10.iOS底层原理之动态方法决议

作者: 牛牛大王奥利给 | 来源:发表于2021-10-03 13:06 被阅读0次

    上一篇学习了方法的慢速查找流程,这一篇文章主要承接上一篇文章,来学习一下如果通过方法的慢速查找还没有找到,那么接下来苹果又对消息处理进一步的做了什么操作,也就是下面要学习的,动态方法决议。

    我们先通过一个🌰来直观的感受下表现,示例代码如下:

    @interface LGTeacher : NSObject
    - (void)helloWorld;
    @end
    @implementation LGTeacher
    - (instancetype)init{
        if (self == [super init]) {
            return self;
        }
        return nil;
    }
    @end
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGTeacher * t1 = [LGTeacher new];
            [t1 helloWorld];
        }
        return 0;
    }
    

    在LGTeacher的interface中写了一个方法helloWorld,但是在.m文件中并没有实现,我们运行一下查看下打印,如下:

    image.png
    可以看到我们平时比较常见的一个报错:unrecognized selector sent to instance
    我全局直接通过搜报错提示的文字unrecognized selector,找到方法objc_defaultForwardHandler
    image.png
    或者从方法的慢速查找出发,在赋值imp时查看调用的方法,查找方法时通过lookUpImpOrForward->_objc_msgForward_impcache->__objc_msgForward->__objc_forward_handler然后最后来到objc_defaultForwardHandler方法:
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    以上是方法未找到的最后结果就会报这个很常见的错误unrecognized selector,我们再来看一下来到objc_defaultForwardHandler方法之前苹果做了什么处理。

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
        //此处省略方法的慢速查找源码 
        //查完之后来到这个判断
        // No implementation found. Try method resolver once. 源码注释,未找到实现,再尝试一次方法处理。
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
              behavior ^= LOOKUP_RESOLVER;
              return resolveMethod_locked(inst, sel, cls, behavior);
          }
    }
    
    enum {
        LOOKUP_INITIALIZE = 1,
        LOOKUP_RESOLVER = 2,
        LOOKUP_NIL = 4,
        LOOKUP_NOCACHE = 8,
    };
    

    如果走完慢速查找流程之后还是没有找到方法,根据behavior的赋值还有LOOKUP_RESOLVER的值,流程会执行到方法
    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 (!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);
    }
    

    这个方法里会有一个判断! cls->isMetaClass()。
    当前类不是元类,那么调用方法resolveInstanceMethod
    当前类是元类,那么调用方法resolveClassMethod,如果在resolveClassMethod依旧没有对方法的处理,那么再调用
    resolveInstanceMethod

    resolveInstanceMethod
    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        SEL resolve_sel = @selector(resolveInstanceMethod:);
    
        if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
            // Resolver not implemented.
            return;
        }
    
        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 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));
            }
        }
    }
    

    先去走方法lookUpImpOrNilTryCache->_lookUpImpTryCache,也就是查缓存,判断此时需要发送的消息有没有被动态的加入到cache中,如果没有处理,直接返回;
    如果已经添加进去了,那么接着再帮忙发一次消息objc_msgSend;
    通过 IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);拿到对应的实现;
    如果imp存在已经被实现了,那么打印 RESOLVE: method dynamically resolved to相关,并且拿到imp结束调用;
    如果没有实现,打印"RESOLVE: +[ resolveInstanceMethod:] returned YES ", but no new implementation of was found,没有动态的添加相关的实现。继续会走其他的流程。

    resolveClassMethod
    static void resolveClassMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        ASSERT(cls->isMetaClass());
    
        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) {
                _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));
            }
        }
    }
    

    以上流程和resolveInstanceMethod相似,只不过现在查询的是isMetaClass元类中的cache还有慢速查找查的是元类的方法列表,也就是类方法。

    resolveInstanceMethod的使用

    下面我们通过resolveInstanceMethod来解决下当前示例的崩溃问题,resolveInstanceMethod这个方法苹果是默认帮你实现了的,返回的是no。实现代码如下

    image.png
    发现实际打印了resolveInstanceMethod中的日志。
    但是打印了两次!为啥会打印两次?我也是一脸懵逼,打断点调试了一下,第一次是走的cache查找->uncached->lookupImpOrForward->resolveInstanceMethod->msg之后打印了一次:--来到了_dynamicContextEvaluation:patternString:--
    (然后打印过之后,又来到了方法lookUpImpOrNilTryCache->_lookUpImpTryCache,然后就没看明白了🐶。。。。。。。🤦🏻)
    resolveInstanceMethod动态添加helloworld方法
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"--来到了%@--",NSStringFromSelector(sel));
        if (@selector(helloWorld) == sel) {
            IMP imp = class_getMethodImplementation(self , @selector(helloWorld));
            Method meth = class_getInstanceMethod(self , @selector(helloWorld));
            const char * type = method_getTypeEncoding(meth);
            return  class_addMethod(self ,sel, imp, type);;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    image.png

    成功打印很多次,一万多次Σ(⊙▽⊙"a。(没搞明白为啥打印那么多次。)

    resolveClassMethod动态添加helloworld方法
    +(BOOL)resolveClassMethod:(SEL)sel{
      if (@selector(helloWorld) == sel) {
        NSLog(@"resolveClassMethod--进入%@--",NSStringFromSelector(sel));
        IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(test));
        Method meth = class_getClassMethod(object_getClass([self class]) , @selector(test));
        const char * type = method_getTypeEncoding(meth);
        return  class_addMethod(object_getClass([self class]) ,sel, imp, type);;
      }
        return [super resolveClassMethod:sel];
    }
    
    + (void)test{
        NSLog(@"testtesttest");
    }
    
    image.png
    动态方法决议的应用

    可以做一个nsobject的分类,专门来处理未实现的方法,动态的添加方法,减少app的crash,这种变成方式叫aop,面向切面编程。

    aop与oop

    AOP:Aspect Oriented Programming,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

    OOP:(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

    总结

    方法的调用,如果通过慢速消息查找没有找到,那么会来到动态方法决议阶段,苹果给了补救崩溃的两次机会一个是resolveInstanceMethod,希望你在这个方法里把未实现的方法动态的处理一下,避免崩溃的发生。或者通过resolveClassMethod来处理。这里resolveInstanceMethod和resolveClassMethod两个方法的意义是从哪里开始查找,如果实现了resolveClassMethod那么方法查找是从metaClass的方法列表里进行查找,如果实现resolveInstanceMethod,是从类的方法列表里面开始查找,在程序的底层没有区分类方法和对象方法。如果以上两个方法都没有处理消息,那么将会来到消息转发阶段,下一篇文章进行分析。

    相关文章

      网友评论

          本文标题:10.iOS底层原理之动态方法决议

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