重拾runtime

本文将会通过仔细体会方法调用过程,系统的复习runtime 寻找IMP 及Method的过程

lookUpImpOrForward

可以说这个方法是runtime 寻找IMP的根基,下面我们来看一下它的实现

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;

// debug 模式下的 assert 进行 unlock:
runtimeLock.assertUnlocked();

// Optimistic cache lookup
//如果使用缓存(cache 参数为 YES),那就调用 cache_getImp 方法从缓存查找 IMP
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}

// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.

// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.

runtimeLock.lock();
checkIsKnownClass(cls);

if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}

//如果是第一次用到这个类且 initialize 参数为 YES
//(initialize && !cls->isInitialized()),需要进行初始化工作,
//也就是开辟一个用于读写数据的空间。先对 runtimeLock 写操作加锁,
//然后调用 cls 的 initialize 方法。如果 sel == initialize 也没关系,
//虽然 initialize 还会被调用一次,但不会起作用啦,
//因为 cls->isInitialized() 已经是 YES 啦。
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again

// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}


retry:
runtimeLock.assertLocked();

// Try this class's cache.

imp = cache_getImp(cls, sel);
if (imp) goto done;

// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//沿继承链寻找
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}

// Superclass cache.
// search_method_list
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
//填入缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}

// No implementation found. Try method resolver once.

//动态方法解析
if (resolver && !triedResolver) {
runtimeLock.unlock();
resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.

//把imp 转换为转发类型
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

done:
runtimeLock.unlock();

return imp;
}

梳理如下流程图
图片

从源码可以看出,lookUpImpOrForward并没有直接参与消息的转发,即:objc_msgForward。改方法只负责返回Imp。当然这个Imp 的实际类型可能是_objc_msgForward_impcache

lookUpImpOrNil

内部调用lookUpImpOrForward,和上面的方法相比,此方法不会返回_objc_msgForward_impcache,而是nil

search_method_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
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
//是否已经被 FixedUp(sorted)
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);

if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
//二分法查找
return findMethodInSortedMethodList(sel, mlist);
} else {
//遍历
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}

#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif

return nil;
}

其中isFixedUp为runtime维护的一个变量,通过prepareMethodLists来更新,而这个方法在attachCategories阶段(category与主类和其他分类方法汇总的时机),和addMethod时(其实也就是当methodlist可能变化时)。

几个获取Imp(Method)的方法

class_getMethodImplementation

class_lookupMethodclass_getMethodImplementation是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;

if (!cls || !sel) return nil;

imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}

return imp;
}

会返回包含_objc_msgForward的imp

method_getImplementation + class_getInstanceMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;

// This deliberately avoids +initialize because it historically did so.

// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.

#warning fixme build and search caches

// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches
//此处并没有返回imp,而是调用_class_getMethod
_class_getMethod -> getMethod_nolock -> getMethodNoSuper_nolock -> search_method_list
return _class_getMethod(cls, sel);
}

不会返回包含_objc_msgForward的imp

代码验证
图片

触发转发的时机

MethodTableLookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// __class_lookupMethodAndLoadCache3 -> lookUpImpOrForward
MethodTableLookup %a1, %a2 // r11 = IMP

//此时 CPU 的状态寄存器的内容来决定转换成哪个。如果是 NE(Not Equal) 则转换成 //_objc_msgForward_stret,反之是 EQ(Equal) 则转换成 _objc_msgForward

cmp %r11, %r11 // set eq (nonstret) for forwarding
jmp *%r11 // goto *imp



// objc_msgSend_stret
MethodTableLookup %a2, %a3 // r11 = IMP
test %r11, %r11 // set ne (stret) for forward; r11!=0
jmp *%r11 // goto *imp

_objc_forward_handler

消息转发过程是现将 _objc_msgForward_impcache强转成 _objc_msgForward_objc_msgForward_stret,再分别调用_objc_forward_handler_objc_forward_handler_stret 最终调用___forwarding___

___forwarding___

反编译结果

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

function ____forwarding___ {
r13 = rdi;
var_-48 = *___stack_chk_guard;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rbx = *(r13 + rcx * 0x8);
var_-64 = *(r13 + rcx * 0x8 + 0x8);
r15 = rcx * 0x8;
if (rbx >= 0x0) goto loc_12810a;

loc_1280d9:
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = rcx ^ rbx;
rax = rcx >> 0x3c & 0x7;
if (rax == 0x7) {
rax = (rcx >> 0x34 & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_12849a;

loc_12810a:
var_-72 = r15;
r15 = rsi;
var_-56 = r12;
rax = object_getClass(rbx);
r12 = rax;
var_-80 = class_getName(rax);
r14 = @selector(forwardingTargetForSelector:);
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_1281a7;

loc_128142:
rax = _objc_msgSend(rbx, r14);
if ((rax == 0x0) || (rax == rbx)) goto loc_1281a7;

loc_12815c:
r12 = var_-56;
r15 = var_-72;
if (rax >= 0x0) goto loc_12819a;

loc_128169:
rdx = *_objc_debug_taggedpointer_obfuscator;
rdx = rdx ^ rax;
rcx = rdx >> 0x3c & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x34 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_128497;

loc_12819a:
*(0x0 + r15) = rax;
r13 = 0x0;
goto loc_1284ca;

loc_1284ca:
if (*___stack_chk_guard == var_-48) {
rax = r13;
}
else {
rax = __stack_chk_fail();
}
return rax;

loc_128497:
rbx = rax;
goto loc_12849a;

loc_12849a:
rax = _getAtomTarget(rbx);
r14 = rax;
*(r13 + r15) = rax;
___invoking___(r12, r13, r13, 0x400, 0x0);
if (*r13 == r14) {
*r13 = rbx;
}
goto loc_1284ca;

loc_1281a7:
var_-56 = rbx;
if (strncmp(var_-80, "_NSZombie_", 0xa) == 0x0) goto loc_128509;

loc_1281c8:
rbx = @selector(methodSignatureForSelector:);
var_-72 = r13;
if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_12855a;

loc_1281e6:
r12 = var_-56;
rax = _objc_msgSend(r12, rbx);
if (rax == 0x0) goto loc_1285ba;

loc_128203:
r14 = rax;
rax = [rax _frameDescriptor];
rbx = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != r15) {
rax = sel_getName(var_-64);
rsi = " not";
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.");
}
var_-80 = rbx;
if (class_respondsToSelector(object_getClass(r12), @selector(_forwardStackInvocation:)) != 0x0) {
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
}
r13 = [NSInvocation requiredStackSizeForSignature:r14];
rdx = *____forwarding___.invClassSize;
r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
memset(r12, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r12);
var_-64 = r13;
[r12 _initWithMethodSignature:r14 frame:var_-72 buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
[var_-56 _forwardStackInvocation:r12];
r15 = 0x1;
}
else {
rbx = @selector(forwardInvocation:);
if (class_respondsToSelector(object_getClass(r12), rbx) != 0x0) {
rax = [NSInvocation _invocationWithMethodSignature:r14 frame:var_-72];
rdi = r12;
r12 = rax;
_objc_msgSend(rdi, rbx);
}
else {
r12 = 0x0;
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", 0x0, object_getClassName(0x0));
}
var_-64 = 0x0;
r15 = 0x0;
}
rax = var_-80;
if (r12->_retainedArgs != 0x0) {
rax = *rax;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rdx = *(int32_t *)(rax + 0x1c);
rsi = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rsi + var_-72 + rdx), *(rsi + rdx + r12->_frame), *(int32_t *)(*rax + 0x10));
}
}
rax = [r14 methodReturnType];
rbx = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
r13 = r12->_retdata;
if (r15 != 0x0) {
r13 = [[NSData dataWithBytes:r13 length:var_-64] bytes];
[r12 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm{ fld tword [r13] };
}
}
else {
r13 = ____forwarding___.placeholder;
if (r15 != 0x0) {
[r12 release];
}
}
goto loc_1284ca;

loc_1285ba:
rax = sel_getName(var_-64);
r15 = rax;
rax = sel_getUid(rax);
if (rax != var_-64) {
r8 = rax;
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_-64, r15, r8);
}
rbx = @selector(doesNotRecognizeSelector:);
if (class_respondsToSelector(object_getClass(var_-56), rbx) != 0x0) {
rax = _objc_msgSend(var_-56, rbx);
asm{ ud2 };
}
else {
rax = _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", var_-56, object_getClassName(var_-56));
asm{ ud2 };
}
return rax;

loc_12855a:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(var_-56);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_-56, r14, object_getClassName(var_-56));
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_-56, r14);
}
goto loc_1285ba;

loc_128509:
if (*(int8_t *)___CFOASafe != 0x0) {
___CFRecordAllocationEvent(0x15, var_-56, 0x0, 0x0, 0x0);
}
rax = _CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p", var_-80 + 0xa, sel_getName(var_-64), var_-56);
asm{ ud2 };
return rax;
}

这里有一份前人的梳理的伪代码,如下

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

int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);

// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwarding != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}

// 僵尸对象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}

// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

[receiver forwardInvocation:invocation];

void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}

SEL *registeredSel = sel_getUid(selName);

// selector 是否已经在 Runtime 注册过
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}

// The point of no return.
kill(getpid(), 9);
}

这么一大坨代码就是整个消息转发路径的逻辑,概括如下:

  • 先调用forwardingTargetForSelector方法获取新的 target作为receiver 重新执行selector,如果返回的内容不合法(为nil或者跟旧receiver 一样),那就进入第二步。
  • 调用methodSignatureForSelector获取方法签名后,判断返回类型信息是否正确,再调用 forwardInvocation执行NSInvocation对象,并将结果返回。如果对象没实现 methodSignatureForSelector方法,进入第三步。
  • 调用doesNotRecognizeSelector方法。

总结

  • lookUpImpOrForward 的原理(寻找IMP的策略),同时触发 +initialize 的时机,及触发动态方法解析的时机
  • class_getMethodImplementation 不会触发消息转发,而是返回带有转发标记的Imp
  • class_getInstanceMethod class_getClassMethod 无法触发消息转发,因为真实的实现为:_class_getMethod
  • _objc_msgForward 的过程