消息查找流程(下)- 方法列表的查找(慢速发送流程)

消息查找流程(下)- 方法列表的查找(慢速发送流程)

IOS小彩虹2021-08-26 4:44:33120A+A-

经过上一篇 消息快速发送之 objc_msgSend 的分析 对调用方法的时候,通过汇编查询 Cache 如果 缓存命中 就直接进行发送。

但是上一篇的结尾说到,如果没有命中缓存就需要走慢速流程,也就是来到 _lookUpImpOrForward 函数里面,接下来就是对慢速流程的探索。

1、进入慢速转发 _lookUpImpOrForward

这里没有写东西是以为 818.2 源码改动了,所以改了标题并且删除了原来的文字,主要发懒不想再排版了,哈哈。

2、lookUpImpOrForward()函数的探索

看函数名称就知道这里是查找方法的实现或者消息转发,一起进入这个函数看看吧,这个函数很长,我们慢慢分析。

/** * lookUpImpOrForward。 * 标准IMP查找。 * inst是cls或其子类的一个实例,如果不知道,则为nil。 * 如果cls是一个未初始化的元类,那么非空的inst会更快。 * 可能返回_objc_msgForward_impcache。用于外部使用的imp必须转换为_objc_msgForward或_objc_msgForward_stret。 */

NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

	//如果没有初始化类
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
  
    // runtimeLock 在isrealize和isInitialized检查过程中被持有,以防止对并发实现的竞争。
    // runtimeLock 在方法搜索过程中保持,使方法查找+缓存填充原子相对于方法添加。
    // 否则,可以添加一个类别,但是无限期地忽略它,因为在代表类别的缓存刷新之后,缓存会用旧值重新填充。
    // 上方的说明就是对这里加锁的解释
    runtimeLock.lock();
    
    
    // 如果运行时知道这个类(位于共享缓存中,加载的图像的数据段中,或者已经用obj_allocateClassPair分配了),
    // 则返回true,
    // 如果没有就崩溃了
    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;


    //获取锁后,代码再次查找类的缓存,但绝大多数情况下,证据显示大多数情况下这是一个遗漏,因此时间损失。
    //唯一没有执行某种缓存查找的代码路径是class_getInstanceMethod()。

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            
            // 先查询一遍缓存 如果 imp 存在,就直接返回了
            imp = cache_getImp(curClass, sel);
            
            // 如果imp 存在就跳转 done_unlock,done_unlock里面就2行代码,
            if (imp) goto done_unlock;
            
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 在该对象的所属的类的方法列表中查找
            
            //使用二分查找法
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                
                goto done;
            }

            //把curClass 指向父类 
            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);
        
        ///低概率找不到
        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;
        }
    }

    // No implementation found. Try method resolver once.
    // 没有找到就进行消息转发
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        
        ///转发
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 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);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

3、 二分查找法的分析 getMethodNoSuper_nolock ()

苹果在查找方法列表的时候使用了 二分查找法,二分查找法有一个前提条件就是 有序数组,我们经常能看到苹果将方法的 SEL sel 强转成整形,这里整形就是有序的~。

有了这个前提,我们看看 getMethodNoSuper_nolock 的代码:

1、getMethodNoSuper_nolock()

/*********************************************************************** * getMethodNoSuper_nolock * fixme * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {
    runtimeLock.assertLocked();

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

    //二分查找
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

2、findMethodInSortedMethodList ()

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) {
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

分析一下上述逻辑:

 /** * count : 初始值为方法列表的个数 假设 48 * 1、如果 count != 0; 循环条件每次 右移一位 也就是说 除以 2; * 2、 第一次进入 从一半开始找起,如果 keyValue > probeValue 那么在右边,否则在左边; * 3、 第二次是从 12 开始找起,也不满足 keyValue > probeValue 的条件; * 4、 第二次从 6 开始找起,满足条件 keyValue > probeValue,将初始值移动到当前 6 的后一位 也就是从 7 开始查找,然后count--, 可以看到当前 count = 5 ,然后在对 > 6 且 < 12 进行查找, 也就是 7 - 11 ,count >> 1 为 2, 7+2 = 9,刚好是 7 - 11 的中心。 * 5、这就是 2分查找法,但是前提是有序数组。 */

4、没有实现的方法 Xcode 是怎么崩溃的?

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
    //...
    
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
			//...
        } else {
            // curClass method list.
            // 在该对象的所属的类的方法列表中查找
            
            //...

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

       //...

        // Superclass cache.
        // 寻找父类的缓存中有没有
        imp = cache_getImp(curClass, sel);
        
        
        //...
        
        ///大概率能找到
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    // 没有找到就进行消息转发
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        
        ///转发
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
        //如果找到了就填充到缓存中
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

上方方法列表的查找就不需要多说了,我们看到了 resolveMethod_locked 消息转发,我们下一篇说明,还有一段就是 (curClass = curClass->getSuperclass()) == nil) imp = (IMP)_objc_msgForward_impcache; ,这是一段汇编。搜索 _objc_msgForward_impcache,看到如下代码:

    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b	__objc_msgForward

    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_forward_handler 函数,继续搜索,得到如下结果:

void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

// Default forward handler halts the process.
__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);
}

呵~,这不就是我们经常看到的 Xcode 找不到方法的崩溃信息吗?

到这里,消息查找流程就全部完成了,下一篇会探索 如果方法没有实现,OC 是怎么消息转发的。

下一篇:消息转发

PS:可以运行的并且不断进行注释的objc 源码地址

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们