美文网首页iOS
iOS-动态方法决议 & 消息转发

iOS-动态方法决议 & 消息转发

作者: Summit_yp | 来源:发表于2021-07-06 16:29 被阅读0次

    iOS-慢速方法查找iOS-快速方法查找中我们分别提到了objc_msgSend的快速查找和慢速查找,如果经历这两步仍未找到该方法的imp会怎么样呢?
    Apple给了我们两次补救的机会,
    动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
    消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
    如果这两次机会都没有做任何操作,就会报我们日常开发中常见的方法未实现的崩溃报错——unrecognized selector sent to
    我们来看看是怎么从慢速查找进入到第一个机会——动态方法决议的吧。

    动态方法决议入口
    康康该方法的源码
    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);
    }
    

    先看看对象方法的动态决议源码

    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))) {//查找resolveInstanceMethod是否实现,其实源码中NSObject实现了该方法。
            // Resolver not implemented.
            return;
        }
       //消息转发,执行resolveInstanceMethod方法
        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));
            }
        }
    }
    

    我们来代码验证一下,给LGPerson.h添加如下代码

    @interface LGPerson : NSObject
    
    + (void)say1;//只声明,未实现
    + (void)say4;
    
    - (void)say2;
    - (void)say3;//只声明,未实现
    @end
    

    LGPerson.m添加如下代码

    @implementation LGPerson
    
    //- (void)say1
    //{
    //  NSLog(@"%s",__func__);
    //}
    - (void)say2
    {
      NSLog(@"%s",__func__);
    }
    //- (void)say3
    //{
    //  NSLog(@"%s",__func__);
    //}
    + (void)say4
    {
      NSLog(@"%s",__func__);
    }
    
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(say3)) {
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
        }
        return [super resolveInstanceMethod:sel];
    }
    @end
    

    main中调用say3,执行结果如下:

    image.png
    可以看到resolveInstanceMethod执行了两次,why?
    -第一次的“来了”是在查找say3方法时会进入动态方法决议
    -第二次“来了”是在慢速转发流程中调用了CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,会再次进入动态决议
    可通过lldbbt命令看看堆栈信息验证
    image.png

    如果想抓住这个防止崩溃的机会,可以修改resolveInstanceMethod代码如下

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(say3)) {
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
            //获取say2方法的imp
            IMP imp = class_getMethodImplementation(self, @selector(say2));
            //获取say2的实例方法
            Method sayMethod  = class_getInstanceMethod(self, @selector(say2));
            //获取say2的签名
            const char *type = method_getTypeEncoding(sayMethod);
            //将sel的实现指向say2
            return class_addMethod(self, sel, imp, type);
        }
    //
        return [super resolveInstanceMethod:sel];
    }
    

    相关文章

      网友评论

        本文标题:iOS-动态方法决议 & 消息转发

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