9-慢速查找流程

前言

之前有讲到过,OC当中几乎所有的方法调用到底层都是消息的发送 objc_msgSend ,也探讨了如何从cache 中通过 cmd 获取 imp 这些流程。但是当我们从缓存当找不到 imp 的时候又该怎么办呢?
前面我们在class 的探讨中提到过 bits 当中存储量很多的属性、方法等。
前面我们在 objc_msgSend() 的查找过程中提到 objc_msgSend_uncached 这个方法,这个方法表示缓存查找不到,那么我们就从 objc_msgSend_uncached 这个方法入手。

汇编缓存找不到

通过搜索 __objc_msgSend_uncached 我们可以找到 MethodTableLookup 才是关键

MethodTableLookup

于是我们去找 MethodTableLookup 这个方法,在这个方法里面我们最终找到了由 C++ 实现的 lookUpImpOrForward 方法。

.macro MethodTableLookup 
    SAVE_REGS MSGSEND 
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16     //赋值,把x16( cls ) 赋值给 x2
    mov x3, #3      // x3 = 3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0
// 由于这里吧 imp 放在了x0里面,所以x0 应该就是上一个 跳转的事件的返回值。所以我们的直接就去寻找 _lookUpImpOrForward,
// 但是全局搜索 _lookUpImpOrForward 过程中发现没有,于是乎我们就搜索 lookUpImpOrForward
// 发现在 objc-runtime-new 里面定义了 lookUpImpOrForward 这个方法。

    RESTORE_REGS MSGSEND
.endmacro

这里也说明了,我们 objc_msgSend 发送消息通过 cmd 获取 imp 的过程是先用汇编在 cache 里面查找,如果找不到了又会采用 C++ 继续查找。

接下来我们去看 lookUpImpOrForward 这个过程。
通过整个方法结果看到最后是一个 return imp; 的过程,所以我们的整个流程就是去查找 imp 什么时候赋值的。于是有了下面这段代码

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
......
/// 这里是个死循环 for (<#initialization#>; <#condition#>; <#increment#>)  通过这个定义可以知道, condition 没有,也就是没有终止条件。
    for (unsigned attempts = unreasonableClassCount();;) {
        
        /// isConstantOptimizedCache 这个方法从其具体的定义和实现的判断(objc_cache.mm 里面)只有在真机的环境下才会有可能存在。
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            /// 这里也印证了只有真机的环境才需要这样做。有可能在查找的过程中有了缓存,那么直接返回。
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            /// 这里才是真正的去获取 imp的流程
            
            if (meth) {
                imp = meth->imp(false);
                goto done;      /// 获取到了imp 直接 goto done
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        /// 如果还是获取不到那么就去从父类厘米获取,然后又走cache 那套流程这样形成一个递归像上的流程。
        /// 如果一旦获取到了,那么应该走上面的 goto done, 下面的 goto done 判断应该只是一个放置意外吧。
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
......
    return imp;
}

通过上面的分析,我们最终找打了 getMethodNoSuper_nolock -> search_method_list_inline 这个方法, 最后找到了 findMethodInSortedMethodList 这个方法。findMethodInSortedMethodList 这个方法就是我们的查找流程,由于是排好序的,所以按照最优解就是二分查找法。于是就到了我们二分查找法去查找 imp 的过程了。

慢速查找流程

是应该去找父类还是去找方法列表。

在整个查找的主线中有点小问题

二分查找

上面最终找到了 findMethodInSortedMethodList 这个方法,那么我们去看看 findMethodInSortedMethodList 的实现。

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin(); /// 获取第一个
    auto base = first;
    decltype(first) probe;  /// 这个是我们想要返回的结果。

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    /**
     假如我们一共有 8 个方法,即 count = 8 , 那么 probe 就应该是从 0 ~7 当中,假定结果为7,key = 7,最后一个
     即:base = 0, probe = 0,key = 7, count = 8
     
     那么第一次for 循环相当于
     for (count = 8, count !=0, count = 4)    count >>= 1  ->   count = count >> 1 ->  1000 >> 1 = 0100 = 4
     count = 8,   base = 0;
     probe = 0 + 4 = 4;
     4 != 7  所以查找失败
     由于 7 > 4 ,所以 base = 5 , count = 3
     
     进入第二次循环
     coun = 3, base = 5
     probe = base + count >> 1 = 5 +  1 = 6;
        由于 6 != 7
     所以没查找失败
     由于 7 > 6  , 所以 count = 2-1 = 1, base = probe + 1 = 6 + 1 = 7
     
     第三次循环
     count = 1, base = 7
     probe = base + count >> 1 = 7+0 = 7
     由于7 == 7 ,所以查找成功
     
     */
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        /// 获取probe 的name ,其实就是 SEL
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        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)getName((probe - 1))) {
                /// 这里就是 category 的同名方法是怎么存的就怎么取。也就是最后编译的同名方法在前面,这个就是循环取最前面的一个。
                probe--;
            }
            /// 从结果看,这里是有这个才是有用的返回,那么结果必然会到这里。
            return &*probe;
        }
        
        //如果没有找到, 并且value 大于当前查找的 key,说明在后面,那么下次的循环就变成
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
} 

同理,比如我们要找小端位置的结果呢?如下所示。

如果我们查找小的呢,比如0号位置。
     base = 0, probe = 0,key = 0, list.count = 8
     那么第一次为
     for (8, 8 != 0, 8 >>= 1)
     probe = base + count >> 1 = 0 + 4
     因为 4 != 0 ,所以查找失败
     由于 0 > 4 不成立,所以 base 和 count 不变
     
     第二次循环
     count = 4, base = 0
     probr = base + count >> 1 = 0 + 2 = 2
     因为 2 != 0 所以查找失败
     由于 0 > 2 不成立, 所以 base 和 count 不变
     
     第三次
     count  = 2, base = 0
     probr = base + count >> 1 = 0 + 1 = 1
     因为 1 != 0 所以查找失败
     由于 0 > 1 不成立, 所以 base 和 count 不变
     
     第四次
     count = 1, base = 0
     probr = base + count >> 1 = 0 + 0 = 0
     因为 0 == 0 ,所以查找成功,返回
     

通过上面的结果,我们找到了有可能找到了imp, 但是如果没有找到呢?
看看几个判断.

  • 1、判断 getSuperclass 是否为空,如果为空,那么就执行 forward_imp。
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
  • 2、imp = cache_getImp(curClass, sel); 递归像父类查找

到此处,如果imp 存在,那么我们一定可以找到,然后执行 goto done;

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }

log_and_fill_cache 这个最后又会执行 cache.insert

总结:

这里需要一张流程图

遗留问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容