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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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;
8 runtimeLock.assertUnlocked();
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 }
17 // runtimeLock is held during isRealized and isInitialized checking
18 // to prevent races against concurrent realization.
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();
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);
38 if (slowpath(!cls->isRealized())) {// cls 是否已实现,否则去将类信息进行处理 类元类方法全部要处理好的 --> 为了后面的方法查找
39 cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
40 // runtimeLock may have been dropped but is now locked again
41 }
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
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 }
54 runtimeLock.assertLocked();
55 curClass = cls;
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().
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 继续向下走
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 }
88 // Superclass cache.
89 // 找父类的缓存
90 /**
91 CacheLookup GETIMP, _cache_getImp
92 */
93 imp = cache_getImp(curClass, sel);
94 /*
95 STATIC_ENTRY _cache_getImp
97 GetClassFromIsa_p16 p0
98 CacheLookup GETIMP, _cache_getImp // GETIMP,cache查找的参数是GETIMP,checkMiss
100 LGetImpMiss:// cache 中没找到直接返回0
101 mov p0, #0
102 ret
104 END_ENTRY _cache_getImp
105 */
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 }
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 }
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 /**
1603 - 01 02 03 04 05 06 07 08
17 probe = 1 + 8>>1 = 1+ 4 = 5
18 判断第5个方法是否是要找的方法
19 不是:对比 要找的方法 和 当前方法 位置谁大 03>05?false
21 开始回第一步
22 probe = 1+ count>>1 = 1 + 4>>1 = 1+2 = 3
23 继续判断...
24 02 == 03 false
25 02 > 03 false
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);
36 uintptr_t probeValue = (uintptr_t)probe->name;
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 }
48 if (keyValue > probeValue) {
49 base = probe + 1;
50 count--;
51 }
52 }
54 return nil;
55 }