iOS 底层原理 - 消息查找流程

2020-03-08  本文已影响0人  yan0_0

我们从之前的方法的本质objc_msgSend分析一篇中得知了解到objc_msgSend发送消息进入到了汇编,找不到缓存的时候就会来到__objc_msgSend_uncached -- > MethodTableLookup --> __class_lookupMethodAndLoadCache3,现在我们通过断点调试来分析一下。

断点调试分析

在调用方法处打上断点,开启Always Show Disassembly


屏幕快照 2020-03-07 下午10.51.47.png 屏幕快照 2020-03-07 下午10.55.50.png

然后进入objc_msgSend,往下翻,看到下面有个_objc_msgSend_uncached方法


屏幕快照 2020-03-07 下午10.57.15.png

在这里加断点,继续往下走,找到方法_class_lookupMethodAndLoadCache3


屏幕快照 2020-03-07 下午10.59.37.png

在源码中全局搜索_class_lookupMethodAndLoadCache3方法,找到方法实现

extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);

点进去

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

到此,方法查找成功从汇编过渡到C++,这就是真正的方法查找实现。

代码分析方法查找流程

对象方法测试

对象的实例方法 - 自己有 - 返回自己
对象的实例方法 - 自己没有 - 父类有 - 返回父类的
对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject有 - 返回NSObject的
对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃

类方法测试

类方法 - 自己有 - 返回自己
类方法 - 自己没有 - 父类有 - 返回父类的
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject有 - 返回NSObject的
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 但是有对象方法 - 返回NSObject的实例方法

这里通过一个经典的isa走位图来解释说明


isa流程图.png

源码分析方法查找流程

这里我们直接定位到_class_lookupMethodAndLoadCache3这个方法

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

然后进入lookUpImpOrForward这个方法,这里cache传的值为NO,证明缓存中没有找到,需要进行方法查找,也就是慢速方法查找流程
然后开始分析lookUpImpOrForward这个方法

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   这里cache传的NO,所以不走这里的逻辑
    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();   //这里加了锁,是为了防止同时查找两个方法时返回imp错误
    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;

  //这里大括号意思是为了形成局部域
  // 从当前类上查找,这里是从类的方法列表中查找 IMP。通过 getMethodNoSuper_nolock 查找 Method,找到了之后就调用 log_and_fill_cache 进行缓存的填充,然后返回 imp。
    // Try this class's method lists.
    {
        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.从缓存中查找,找到就返回img找不到就break
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    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 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.
 //如果方法仍然没找到,首先会进行动态方法解析_class_resolveMethod,在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,在这里给我们提供了一次容错的机会,
    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;
}

这里是通过getMethodNoSuper_nolock进行方法列表查找

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

用的二分法快速的查找方法

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

如果所有的方法都找不到,就会来到这里

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);

首先会进行动态方法解析_class_resolveMethod,在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,在这里给我们提供了一次容错的机会。首先会判断cls是不是元类,如果cls不是元类的话,说明调用的是实例方法,那就就会调用_class_resolveInstanceMethod函数,如果是元类的话,说明调用的是类方法,那么就会调用_class_resolveClassMethod函数,并且调用完后会再次查找一下sel的指针,找到了就会返回,如果还是找不到的话会调用_class_resolveInstanceMethod函数,这里可以用上面的isa走位图解释:
类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 但是有对象方法 - 返回NSObject的实例方法

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);
        }
    }
}

如果我们没有做任何处理,那么则进入消息转发阶段,消息转发阶段全局搜索一下该方法。在其汇编方法内部,调用了__objc_msgForward

imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

全局搜索_objc_msgForward_impcache,进入到汇编


屏幕快照 2020-03-08 上午11.05.17.png

然后看到下面执行方法_objc_forward_handler,继续全局搜索,看到的熟悉的找不到方法的报错信息

void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

__attribute__((noreturn)) void 
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);
}

总结

OC的消息机制可以分为一下三个阶段:
消息发送阶段:先是通过objc_msgSend的快速缓存查找,之后再通过对类以及父类的方法列表进行二分法常规查找。
动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现;
消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理,消息转发处理失败之后则报错crash。
之后便是消息转发的功能了,这里先附上流程图,下篇我们再继续分析转发的流程。


消息转发流程.png
上一篇下一篇

猜你喜欢

热点阅读