美文网首页iOS
iOS-底层原理12:消息流程分析之 动态方法决议 & 消息转发

iOS-底层原理12:消息流程分析之 动态方法决议 & 消息转发

作者: AcmenL | 来源:发表于2020-12-02 18:07 被阅读0次

    在上一篇文章iOS-底层原理11:消息流程分析之慢速查找 中,分析了消息慢速查找流程,如果查找不到将进行动态方法决议,如果动态方法决议仍然没有找到实现,则进行消息转发

    案例

    step1: 新建一个LBHPerson类,定义一个实例方法instanceMethod1和一个类方法classMethod1,只声明不实现

    //.h 
    @interface LBHPerson : NSObject
    
    - (void)instanceMethod1;
    + (void)classMethod1;
    
    @end
    
    //.m
    
    @implementation LBHPerson
    
    @end
    
    

    step2:main函数中调用LBHPerson类的实例方法instanceMethod1

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            LBHPerson *person = [LBHPerson alloc];
            
            [person instanceMethod1];
            
        }
        return 0;
    }
    

    step3: 运行结果

    调用类方法

    [LBHPerson classMethod1];
    

    运行结果

    unrecognized selector sent to instance 0xxxxx 找不到方法实现

    这是一个开发中很常见的奔溃问题,先学习这篇文章,然后用动态方法决议和消息转发解决这个问题。

    1. 动态方法决议

    动态方法决议:慢速查找流程未找到方法,会给一次机会

    static NEVER_INLINE IMP
    resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
    {
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
    
        runtimeLock.unlock();
      
        // 对象方法
        if (! cls->isMetaClass()) {
            resolveInstanceMethod(inst, sel, cls);
        } 
        // 类方法
        else {
            resolveClassMethod(inst, sel, cls);
    //为什么要有这行代码? -- 类方法在元类中是对象方法,所以还是需要查询元类中对象方法的动态方法决议
            if (!lookUpImpOrNil(inst, sel, cls)) {
                resolveInstanceMethod(inst, sel, cls);
            }
        }
    
        // 重新查询一次
        return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
    }
    

    分为以下几步:

    part1: 判断cls是否是元类

    • 如果是,调用实例方法的动态方法决议resolveInstanceMethod
    • 如果是元类,调用类方法的动态方法决议resolveClassMethod,如果在元类没有找到或者为空,则在元类实例方法的动态方法决议resolveInstanceMethod中查找, 是因为类方法存储在元类中,是元类的实例方法,所以还需要查找元类中实例方法的动态方法决议

    part2: 如果动态方法决议中,将其实现指向了其他方法,则继续查找指定的imp,即继续慢速查找lookUpImpOrForward流程

    此时 behavior = 1, LOOKUP_CACHE = 4lookUpImpOrForward函数中形参behavior变成了 1 | 4 = 5 ,这决定了进入lookUpImpOrForward后:

    • fastpath(behavior & LOOKUP_CACHE) = 5 & 4 = 4,条件成立,会优先cache_getImp读取一次缓存
    • slowpath(behavior & LOOKUP_RESOLVER) = 5 & 2 = 0,条件成立,不会进入resolveMethod_locked动态方法决议。
    • lookUpImpOrForward会循环遍历cls继承链的所有类的cache和methodList来寻找imp

    流程图:

    1.1 实例方法决议

    step1: 实例方法在快速查找 -> 慢速查找 都没有找到的情况下,会走到 resolveInstanceMethod 方法,源码如下:

    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        SEL resolve_sel = @selector(resolveInstanceMethod:);
    
        // 1. 查找类对象的元类中是否有`resolveInstanceMethod`的imp。
        // (根元类中默认实现了`resolveInstanceMethod`方法,所以永远不会return)
        if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
            return;
        }
        
        // 2. 发送resolve_sel消息
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, resolve_sel, sel);
    
        // 3. 再搜索一次sel的imp
        //(如果在上面resolveInstanceMethod函数实现了sel,我们就拿到imp了,成功将sel和imp写入cls的缓存中)
        IMP imp = lookUpImpOrNil(inst, sel, cls);
        
        // 做Log记录
        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));
            }
        }
    }
    

    分步解析:

    part1: 查找resolve_sel

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
            // Resolver not implemented.
            return;
    }
    

    问题lookUpImpOrNil到底做了什么?
    解答:
    查看lookUpImpOrNil源码

    static inline IMP
    lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
    {
       // behavior = 0, LOOKUP_CACHE = 4, LOOKUP_NIL = 8
       return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
    }
    

    可以看到在lookUpImpOrNil中又调用了lookUpImpOrForward慢速查找流程

    lookUpImpOrForward函数中形参behavior变成了 0 | 4 | 8 = 12, 这决定了进入lookUpImpOrForward后:

    • fastpath(behavior & LOOKUP_CACHE) = 12 & 4 = 4,条件成立,会优先cache_getImp读取一次缓存
    • slowpath(behavior & LOOKUP_RESOLVER) = 12 & 2 = 0,条件成立,不会进入resolveMethod_locked动态方法决议。
    • lookUpImpOrNil中的lookUpImpOrForward会循环遍历cls继承链的所有类的cache和methodList来寻找imp

    判断能否在慢速查找流程中找到resolveInstanceMethod方法实现。实际上根本不会进入if条件,因为在NSObject元类存在resolveInstanceMethod类方法。

    问题:为什么不会进if条件? NSObject元类中存在resolveInstanceMethod类方法能证明吗?

    解答

    /// 遍历方法
    -(void) printMethodes: (Class)cls {
       // 记录函数个数
       unsigned int count = 0;
       // 读取函数列表
       Method *methodList = class_copyMethodList(cls, &count);
        for (int i = 0; i < count; i++) {
            Method method = methodList[i];
            SEL sel = method_getName(method);
            IMP imp = class_getMethodImplementation(cls, sel);
            NSLog(@"method: %@-%p", NSStringFromSelector(sel), imp);
        }
        free(methodList);
    }
    
    //调用
    [self printMethodes:objc_getMetaClass("NSObject")];
    

    运行结果

    NSObject元类方法列表中可以找到resolveInstanceMethod类方法

    part2: 发送resolve_sel消息

    // 2. 消息发送
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    

    当有

    part3: 通过慢速查找流程获取用户调用的方法sel(demo中为instanceMethod1)的方法实现imp

    IMP imp = lookUpImpOrNil(inst, sel, cls);
    
    奔溃修改

    step1:LBHPerson中新增一个lbhInstanceMethod的实例方法,声明并实现

    //.h
    @interface LBHPerson : NSObject
    
    - (void)lbhInstanceMethod;
    
    @end
    
    
    //.m
    @implementation LBHPerson
    
    - (void)lbhInstanceMethod
    {
        NSLog(@"%s",__func__);
    }
    
    @end
    

    step2:LBHPerson类中重写resolveInstanceMethod类方法

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

    运行

    崩溃解决了,实际上这么写是比较鸡肋的,都已经知道某个方法没有实现,那直接实现就好了,当然可以将if条件去掉,所有未实现的方法都走这个实现,那么有没有更好的方法呢?继续往下学习。

    1.2 类方法决议

    类方法在快速查找 -> 慢速查找 都没有找到的情况下,会走到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方法流程类似。

    崩溃解决

    LBHPerson中添加一个lbhClassMethod类方法的,重写resolveClassMethod类方法

    + (void)lbhClassMethod
    {
        NSLog(@"%s",__func__);
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel{
    
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        
        if (sel == @selector(classMethod1)) {
    
            IMP imp = class_getMethodImplementation(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
            Method lgClassMethod1  = class_getInstanceMethod(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
            const char *type = method_getTypeEncoding(lgClassMethod1);
            
            return class_addMethod(objc_getMetaClass("LBHPerson"), sel, imp, type);
            
        }
    
        return [super resolveClassMethod:sel];
    }
    
    1.3 优化

    上面解决方法都是在单独的某个类中重写动态决议方法,这意味着每个类中都需要重写这两个方法,这样太麻烦了,怎么做呢? 相信大家都会。

    • 实例方法: --> 父类 --> 根类 --> nil
    • 类方法 :元类 --> 根元类 --> 根类 --> nil

    如果在当前元类中没有找到方法实现,会沿着它们的继承链向上查找,它们都会经过根类即NSObject

    问题: 是否可以将上述的两个方法统一整合在一起呢?
    解答:是可以的,可以通过NSObject分类的方式来实现统一处理,而且由于类方法的查找,在其继承链,查找的也是实例方法,所以可以将实例方法类方法的统一放在resolveInstanceMethod方法中处理。

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        
        if (sel == @selector(classMethod1)) {
            
            IMP imp = class_getMethodImplementation(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
            Method lgClassMethod1  = class_getInstanceMethod(objc_getMetaClass("LBHPerson"), @selector(lbhClassMethod));
            const char *type = method_getTypeEncoding(lgClassMethod1);
    
            return class_addMethod(objc_getMetaClass("LBHPerson"), sel, imp, type);
        
        }else if (sel == @selector(instanceMethod1)) {
            
            //获取instanceMethod2方法的imp
            IMP imp = class_getMethodImplementation(self, @selector(lbhInstanceMethod));
            //获取instanceMethod2的实例方法
            Method lbhInstanceMethod1  = class_getInstanceMethod(self, @selector(lbhInstanceMethod));
            //获取instanceMethod2的丰富签名
            const char *type = method_getTypeEncoding(lbhInstanceMethod1);
            //将sel的实现指向instanceMethod2
    
            return class_addMethod(self, sel, imp, type);
            
        }
        
    //    return [super resolveInstanceMethod:sel];
        return NO;
    }
    

    这种方式的实现,正好与源码中针对类方法的处理逻辑是一致的,即完美阐述为什么调用了类方法动态方法决议,还要调用对象方法动态方法决议,其根本原因是类方法是元类中的实例方法

    2. 消息转发之快速转发

    我们了解到,如果快速+慢速没有找到方法实现,动态方法决议也不行,就使用消息转发,但是,我们找遍了源码也没有发现消息转发的相关源码,可以通过以下方式来了解:

    • 通过instrumentObjcMessageSends方式打印发送消息的日志
    • 通过hopper/IDA反编译

    2.1 instrumentObjcMessageSends

    通过lookUpImpOrForward --> log_and_fill_cache --> logMessageSend,在logMessageSend源码下方找到instrumentObjcMessageSends的源码实现。
    在main中调用
    instrumentObjcMessageSends打印方法调用的日志信息,有以下两点准备工作

    1、打开 objcMsgLogEnabled 开关,即调用instrumentObjcMessageSends方法时,传入YES

    2、在main中通过extern 声明instrumentObjcMessageSends方法

    extern void instrumentObjcMessageSends(BOOL flag);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            LBHPerson *person = [LBHPerson alloc];
            instrumentObjcMessageSends(YES);
            [person instanceMethod1];
    //        [LBHPerson classMethod];
            instrumentObjcMessageSends(NO);
            
        }
        return 0;
    }
    

    3. 消息转发之慢速转发

    未完

    相关文章

      网友评论

        本文标题:iOS-底层原理12:消息流程分析之 动态方法决议 & 消息转发

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