OC 底层探索 10、objc_msgSend 流程 2 - 慢速查找
我们已经知道消息发送流程首先会走到缓存 cache 里面,那么当缓存中没有查询到消息时 __objc_msgSend_uncached,后续怎么继续执行呢?
一、切入口
__objc_msgSend_uncached --> MethodTableLookup --> _lookUpImpOrForward.
我们通过源码可以看到缓存中找不到会进入 _lookUpImpOrForward 的查找过程,汇编中搜索并未找到此方法:
1、全局搜索‘lookUpImpOrForward’,可以发现此方法是在objc-runtime-new.mm文件中C++实现的;
2、同样也可以通过 show 反汇编方式找到:Debug --> Debug Workflow --> Always show disassembly 勾上
control + stepinto 进入 objc_msgSend:
control + stepinto 进入 _objc_msgSend_uncached:
_lookUpImpOrForward --> objc-runtime-new.mm 的 6116 行。
下面将从 lookUpImpOrForward 为入口进行消息查找流程的探索。
二、慢速查找分析
1、慢速查找流程图:
forward_imp:--> const IMP forward_imp = (IMP)_objc_msgForward_impcache;
这里代码会继续找到汇编里面:--> __objc_msgForward --> _objc_forward_handler :
经典的报错信息!“+ -” 的打印时苹果人为手动添加的,这里其实也可说明在底层并不存在所谓的 +- 方法,都是函数而已,实例方法是类的实例方法,类方法也是元类的实例方法。
2、主要源码
1. 查找的主流程
1 IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) 2 {// cache 中没有找到方法,开始走 lookUpImpOrForward 查找流程 3 // 汇编有调 --> objc_msgForward --> 找不到方法报错信息的处理 4 const IMP forward_imp = (IMP)_objc_msgForward_impcache; 5 IMP imp = nil; 6 Class curClass; 7 8 runtimeLock.assertUnlocked(); 9 10 // Optimistic cache lookup 11 if (fastpath(behavior & LOOKUP_CACHE)) { 12 // 找缓存 - 是为了出现在此过程中方法又被人调用加进缓存了,有缓存了就不必继续慢速找了 13 imp = cache_getImp(cls, sel); 14 if (imp) goto done_nolock;// 找到了,去 done_nolock 15 } 16 17 // runtimeLock is held during isRealized and isInitialized checking 18 // to prevent races against concurrent realization. 19 20 // runtimeLock is held during method search to make 21 // method-lookup + cache-fill atomic with respect to method addition. 22 // Otherwise, a category could be added but ignored indefinitely because 23 // the cache was re-filled with the old value after the cache flush on 24 // behalf of the category. 25 // 注释翻译不如英文准确不翻了 26 runtimeLock.lock(); 27 28 // We don't want people to be able to craft a binary blob that looks like 29 // a class but really isn't one and do a CFI attack. 30 // 31 // To make these harder we want to make sure this is a class that was 32 // either built into the binary or legitimately registered through 33 // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. 34 // 35 // TODO: this check is quite costly during process startup. 36 checkIsKnownClass(cls); 37 38 if (slowpath(!cls->isRealized())) {// cls 是否已实现,否则去将类信息进行处理 类元类方法全部要处理好的 --> 为了后面的方法查找 39 cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); 40 // runtimeLock may have been dropped but is now locked again 41 } 42 43 // initialize 初始化 44 if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { 45 cls = initializeAndLeaveLocked(cls, inst, runtimeLock); 46 // runtimeLock may have been dropped but is now locked again 47 48 // If sel == initialize, class_initialize will send +initialize and 49 // then the messenger will send +initialize again after this 50 // procedure finishes. Of course, if this is not being called 51 // from the messenger then it won't happen. 2778172 52 } 53 54 runtimeLock.assertLocked(); 55 curClass = cls; 56 57 // The code used to lookpu the class's cache again right after 58 // we take the lock but for the vast majority of the cases 59 // evidence shows this is a miss most of the time, hence a time loss. 60 // 61 // The only codepath calling into this without having performed some 62 // kind of cache lookup is class_getInstanceMethod(). 63 64 for (unsigned attempts = unreasonableClassCount();;) {// 死循环,没有出口条件,跳出逻辑在循环内部 65 // curClass method list. 66 Method meth = getMethodNoSuper_nolock(curClass, sel);// 查找方法 67 if (meth) {// 找着了 68 imp = meth->imp; 69 goto done; 70 } 71 // 自己没找着 72 // curClass = superClass 73 // 是nil 则直接 没找着方法把nil的forward_imp赋给imp,并跳出循环 74 if (slowpath((curClass = curClass->superclass) == nil)) { 75 // No implementation found, and method resolver didn't help. 76 // Use forwarding. 77 imp = forward_imp; 78 break; 79 } 80 // superclass 不是 nil 继续向下走 81 82 // Halt if there is a cycle in the superclass chain. 83 // 如果超类链中存在循环,则停止 84 if (slowpath(--attempts == 0)) { 85 _objc_fatal("Memory corruption in class list.");// 类列表的内存污染了 86 } 87 88 // Superclass cache. 89 // 找父类的缓存 90 /** 91 CacheLookup GETIMP, _cache_getImp 92 */ 93 imp = cache_getImp(curClass, sel); 94 /* 95 STATIC_ENTRY _cache_getImp 96 97 GetClassFromIsa_p16 p0 98 CacheLookup GETIMP, _cache_getImp // GETIMP,cache查找的参数是GETIMP,checkMiss 99 100 LGetImpMiss:// cache 中没找到直接返回0 101 mov p0, #0 102 ret 103 104 END_ENTRY _cache_getImp 105 */ 106 107 108 if (slowpath(imp == forward_imp)) {// 109 // Found a forward:: entry in a superclass. 110 // Stop searching, but don't cache yet; call method 111 // resolver for this class first. 112 break; 113 } 114 if (fastpath(imp)) {// 父类中找到了 goto done --> 对此方法进行缓存 115 // Found the method in a superclass. Cache it in this class. 116 goto done; 117 } 118 } 119 120 // No implementation found. Try method resolver once. 121 // 上面找完了没找着方法 动态方法解析 resolver 一次 --> 此方法只会走一次once 122 if (slowpath(behavior & LOOKUP_RESOLVER)) {/*
&:3 & 2 = 0011 & 0010 = 0010
第二次(方法动态处理中会再回来查一遍)再来条件就为false了: 0001 & 0010 = 0000
*/ 123 behavior ^= LOOKUP_RESOLVER;// 异或操作 behavior = 0011^0010 = 0001 124 return resolveMethod_locked(inst, sel, cls, behavior); 125 } 126 127 done: 128 log_and_fill_cache(cls, imp, sel, inst, curClass); 129 runtimeLock.unlock(); 130 done_nolock: 131 if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { 132 return nil; 133 } 134 return imp; 135 }
2. 二分查找方法 list 源码与注解
1 /*********************************************************************** 2 * search_method_list_inline 3 **********************************************************************/ 4 ALWAYS_INLINE static method_t * 5 findMethodInSortedMethodList(SEL key, const method_list_t *list) 6 { 7 ASSERT(list); 8 // list: 方法list是递增排序(类加载时完成的排序)的 即:name 转 unsigned long 类型 对应的数值 是递增的,例:0 1 2 3 4 5 ...... 9 const method_t * const first = &list->first; 10 const method_t *base = first;// 方法list中第一个方法 11 const method_t *probe; 12 uintptr_t keyValue = (uintptr_t)key;// 要查找的方法 sel强转uintptr_t 13 uint32_t count;// 方法数 14 // 二分查找 15 /** 16 找 03 - 01 02 03 04 05 06 07 08 17 probe = 1 + 8>>1 = 1+ 4 = 5 18 判断第5个方法是否是要找的方法 19 不是:对比 要找的方法 和 当前方法 位置谁大 03>05?false 20 21 开始回第一步 22 probe = 1+ count>>1 = 1 + 4>>1 = 1+2 = 3 23 继续判断... 24 02 == 03 false 25 02 > 03 false 26 27 继续 28 probe = 1+ 2>>1 = 1+1 = 2 29 02 == 02 ture 30 while 判断 31 判断找有重名的分类方法 32 */ 33 for (count = list->count; count != 0; count >>= 1) {// count = 8>>1 4>>1 2>>1 34 probe = base + (count >> 1); 35 36 uintptr_t probeValue = (uintptr_t)probe->name; 37 38 if (keyValue == probeValue) {// 判断这个方法是否是要找的方法 39 // `probe` is a match. 40 // Rewind looking for the *first* occurrence of this value. 41 // This is required for correct category overrides. 42 while (probe > first && keyValue == (uintptr_t)probe[-1].name) {// 判断分类方法 是否有重名的方法,有则往前找取分类方法 --> 分类排在前面 43 probe--; 44 } 45 return (method_t *)probe; 46 } 47 48 if (keyValue > probeValue) { 49 base = probe + 1; 50 count--; 51 } 52 } 53 54 return nil; 55 }