本文将会通过仔细体会方法调用过程,系统的复习runtime 寻找IMP 及Method的过程
lookUpImpOrForward
可以说这个方法是runtime 寻找IMP
的根基,下面我们来看一下它的实现
1 |
|
梳理如下流程图
从源码可以看出,lookUpImpOrForward
并没有直接参与消息的转发,即:objc_msgForward
。改方法只负责返回Imp。当然这个Imp 的实际类型可能是_objc_msgForward_impcache
lookUpImpOrNil
内部调用lookUpImpOrForward
,和上面的方法相比,此方法不会返回_objc_msgForward_impcache
,而是nil
search_method_list
1 | static method_t *search_method_list(const method_list_t *mlist, SEL sel) |
其中isFixedUp
为runtime维护的一个变量,通过prepareMethodLists
来更新,而这个方法在attachCategories
阶段(category与主类和其他分类方法汇总的时机),和addMethod
时(其实也就是当methodlist可能变化时)。
几个获取Imp(Method)的方法
class_getMethodImplementation
class_lookupMethod
与class_getMethodImplementation
是一样的
1 | IMP class_getMethodImplementation(Class cls, SEL sel) |
会返回包含_objc_msgForward
的imp
method_getImplementation + class_getInstanceMethod
1 |
|
不会返回包含_objc_msgForward
的imp
代码验证
触发转发的时机
MethodTableLookup
1 |
|
_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 的过程