美文网首页iOS底层
objc_msgSend分析(2)-方法查找

objc_msgSend分析(2)-方法查找

作者: xxxxxxxx_123 | 来源:发表于2020-01-03 18:11 被阅读0次

      上节我们分析了objc_msgSend的快速查找部分(汇编部分),这一节我们来分析其慢速部分,也就是c语言部分。

    方法查找流程

    首先我们实现以下代码:

    @interface TPerson : NSObject
    - (void)sayNB;
    + (void)sayHappay;
    @end
    
    @interface TStudent : TPerson
    - (void)sayHello;
    + (void)sayObjc;
    @end
    
    @interface NSObject (Category)
    - (void)sayMaster;
    + (void)sayEasy;
    @end
    

    以上方法均在.m文件中实现。

    一、实例方法的查找流程

    当我们调用以下代码的时候:

    TStudent *stu = [[TStudent alloc] init];
    [stu sayHello];
    [stu sayNB];
    [stu sayMaster];
    [stu performSelector: @selector(sayHeHe)];
    

    我们会发现前三个方法均会调用成功,但是sayHeHe会崩溃,由此,我们可以知道:

    • 自己有,可以正常调用
    • 自己没有没有而父类拥有,可以去调用父类的方法,依次类推,可以去查找多级父类的方法,直至根类;
    • 自己没有,父类、直至根类都没有,则会崩溃。

    一、类方法的查找流程

    [TStudent sayHappay];
    [TStudent sayObjc];
    [TStudent sayEasy];
    [TStudent performSelector: @selector(sayMaster)];
    [TStudent performSelector: @selector(sayNB)];
    [TStudent performSelector: @selector(sayHeHe)];
    

    运行以上代码,我们会发现前四个方法会正常执行,而sayNB、sayHeHe会崩溃,由此,可以得出:

    • 自己有,可以正常调用
    • 自己没有没有而父类拥有,可以去调用父类的方法,依次类推,可以去查找多级父类的方法,直至根类;
    • 自己没有,父类、直至根类都没有,但是对象方法有,也可以正常调用。因为类方法存在元类中,当我们去寻找方法的时候就是在元类的父类中寻找,而根元类的父类是NSObject,由于对象方法存在类中,所以sayMaster可以正常调用。
    • 自己没有,父类、直至根类都没有,则会崩溃。

    接上一节objc_msgSend汇编流程结束之后,我们进入下列C方法:

    lookUpImpOrForward(cls, sel, obj, 
                       YES/*initialize*/,
                       NO/*cache*/, 
                       YES/*resolver*/)
    
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.lock();
        checkIsKnownClass(cls);
    
        // 准备条件,将当前类、父类的相关信息罗列出列出来
        if (!cls->isRealized()) {
            realizeClass(cls);
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
            // If sel == initialize, _class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        
     retry:    
        runtimeLock.assertLocked();
    
        // Try this class's cache.
    
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists.
        {
        // 从当前类的methodList里面去查找方法
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
            // 找到之后写入缓存,然后返回
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        // 当前类没找到
        // 从父类的缓存和方法列表里面找
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            // 循环遍历当前类的父类
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                // 从父类的缓存中查找imp
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    // 缓存命中的话,如果不是 _objc_msgForward_impcache
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        // 写入当前类的缓存中,然后发返回imp
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                // 在父类的method list 里面查找
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlock();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.lock();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlock();
    
        return imp;
    }
    
    static void checkIsKnownClass(Class cls)
    {
        if (!isKnownClass(cls))
            _objc_fatal("Attempt to use unknown class %p.", cls);
    }
    

    cache_getImp 则是返回汇编的查找部分,用汇编实现

        STATIC_ENTRY _cache_getImp
    
        GetClassFromIsa_p16 p0
        CacheLookup GETIMP
    
    LGetImpMiss:
        mov p0, #0
        ret
    
        END_ENTRY _cache_getImp
    

    __objc_msgForward_impcache 是汇编实现:

    END_ENTRY __objc_msgForward_impcache
        ENTRY __objc_msgForward
    
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17
        
        END_ENTRY __objc_msgForward
    

    抛出异常:

    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;
    
    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
    
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    以上代码可以总结为以下的流程图:

    image

    处理unrecognized selector sent to instance报错

      当我们找不到某一个方法的定义/实现的时候,系统帮我们实现了resolveInstanceMethod作为补救,我们可以在该方法里面实现对崩溃的拦截和进一步处理:

    - (void)showUpdateTips {
        NSLog(@"-----更新-------");
    }
    
    // 动态解析没有处理
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        IMP updateImp = class_getMethodImplementation(self, @selector(showUpdateTips));
        Method updateMethod = class_getInstanceMethod(self, @selector(showUpdateTips));
        const char *type = method_getTypeEncoding(updateMethod);
        return class_addMethod(self, sel, updateImp, type);
    }
    

    总结:
      objc_msgSend进入c语言部分的时候,需要先判断当前类是否是isKnownClass;如果是则继续判断是否是isRealized(),如果不是realizeClass;接着判断类是否初始化,如果没有则进行初始化操作;然后从当前类的缓存获取imp,如果缓存命中则调用imp;没有命中则去类的方法列表里寻找;如果找到就写入缓存并调用;找不到就逐层级遍历当前类的父类;在父类的中寻找和当前类中寻找的原理一样,就不一一赘述;如果在所有父类种还没找到,则调用一次_class_resolveMethod(cls, sel, inst)看看是否进行动态解析处理,然后再执行一次方法查找,如果在遍历结束还是未找到,则会抛出异常unrecognized selector sent to instance。

    相关文章

      网友评论

        本文标题:objc_msgSend分析(2)-方法查找

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