iOS 底层学习9

iOS 底层第9天的学习。今天主要分享内容还是objc_msgSend,在第8天的学习中,分析了object_msgSend 的流程,但有一个地方并没有深入下去,就是当没有找到 sel == 0 时,会进入 __objc_msgSend_uncached,那么这个__objc_msgSend_uncached 到底是如何实现?

objc_msgSend_uncached

  • 搜索 objc_msgSend_uncached
    STATIC_ENTRY __objc_msgSend_uncached // 进入 __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search
    
    MethodTableLookup 
    TailCallFunctionPointer x17 // 意义不大

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search
    
    MethodTableLookup
    ret
  • 分析👆代码,可知 imp.存在 MethodTableLookup 这个里面 ,搜索MethodTableLookup
.macro MethodTableLookup
    
    SAVE_REGS MSGSEND

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0

    RESTORE_REGS MSGSEND

.endmacro
  • mov x17, x0汇编——如果在 x0 里面 必然会返回,x0 是第一位的计数器,也是返回值的存储位置,所以 x0bl _lookUpImpOrForward 的这个返回值里面 => x0 = imp

  • 搜索不到_lookUpImpOrForward 汇编代码。把 _ 去掉,继续搜索找到 代码,接下来就从汇编流程进入到了c/c++ 流程。

  • 当我们缓存里找不到的时候会进入lookUpImpOrForward

那为什么缓存要用汇编,而不用 c/c++ 呢?

  • 汇编更接近于机器语言执行非常的快(让对象能在底层更加快速的在缓存中找到方法
  • 汇编能更加快速的查找优化时间
  • 汇编能更加的动态化。有些方法参数是未知,而 c 语言无法满足。

那为什么lookUpImpOrForward 又要用 c/c++?

  • 因为接下来是一个慢速查找的流程, 缓存里已经找不到了, 就要到 methodlist 去查找方法。

lookUpImpOrForward

  • 我们接下来开始分析 lookUpImpOrForward ,它到底是怎么找到 imp
  • 我们直接分析主要的代码 找到 curClass method list.
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
    imp = meth->imp(false);
    goto done;
}

if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break;
}
  • 进入 getMethodNoSuper_nolock
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)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }
    return nil;
}
  • getMethodNoSuper_nolock 里是一个二维数组的循环去获取 method_t
  • 再进入 search_method_list_inline 看看它是如何获取的
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->isExpectedSize();
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }
}
  • 再进入 findMethodInSortedMethodList
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}
  • 如果是正常的电脑会走 else , return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
  • 继续 findMethodInSortedMethodList 终于看到了如下代码
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;


   // >>>>>>>>>>>>>>> 我们开始进行分析,这个循环到底是如何找到 probe
   //  我们假设  count = 7 => 0111
    for (count = list->count; count != 0; count >>= 1) {  
   // count = 7 进来 , count !=0  继续往下走
        probe = base + (count >> 1);  
  //  base (一号位置) = 0,  (count >> 1) =>   0111 >> 1 =  0011 = 3
  //  probe = 0 + 3 =>  probe  = 3
  // ----------------------------------------- count-- 后在进遍历
  // 这时  base = 4,count = 6,count !=0  继续往下走
  //  count >>= 1 => 0110 >> 1 => 0010 = 2
  // 为什么 count = 2 呢?,
  // 我们前面已经得到了  probe  = 3 ,所以 count 就完美的避开了 3 来到了 2 
  // base = 4,count = 6 , 取值应该在4~6 => 5
  // probe = base + (count >> 1);  =>  probe = 4 + ( 0010 >> 1 = 1) =>  probe = 5
        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.
           // 寻找分类 category , 当分类和主类有相同的方法调用 分类的方法
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;// 返回
        }
        if (keyValue > probeValue) {
        // 如果没能匹配上
            base = probe + 1;
        // 之前的 probe = 3  => base =  3 + 1  => base = 4
            count--;
        //  count--  之前的 count = 7 =>  count = 7
        }
    }
    return nil;
}
  • 根据上面的代码的注释我们可以知道,这个算法已经把二分的查找流程写的太极致了,每一次>>1 都是有他的意义所在。
  • 当我们根据二分查找找到了 method,根据method->imp ,继续往下 goto done
  if (meth) {
         imp = meth->imp(false);
         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);
    }
  • 如果能匹配到 imp
  • done 之后就会进入 log_and_fill_cache => cls->cache.insert(sel, imp, receiver); 插入到缓存中
  • 这时我们可以得出一个结论——在第一次lookup imp的时候,会 objc_msgSend 从缓存中查找,如果缓存里没有就会在rorw里进入慢速查找,如果慢速没找到就会error,如果找到了就会在缓存里insert。当前我们下次在去调用时就会 objc_msgSend 从缓存中快速lookup imp

如果不能匹配到 imp,继续👇代码

// 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;
}
  • 就会进入父类 cache_getImp ,这里的 cache_getImp 其实就是汇编流程跟 objc_msgSend 是一样的。
    STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0, 0
    CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
  • 分析cache_getImp 中的 curClass 何时被赋值
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break;
}
  • 当在父类进行慢速查找时,没找到的话会进入父类的父类,其实就是一个递归。但递归到最后还没找到返回 nil时就会进入 forward_imp

流程&总结

查找imp流程.jpg
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容