动态方法决议
接着前面我们讲到了resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//判断是否是元类,元类用resolveClassMethod处理;非元类resolveInstanceMethod处理
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
判断当前类是否为元类,元类用resolveClassMethod处理;非元类resolveInstanceMethod处理。处理完之后调用lookUpImpOrForward
继续查找imp。
我们先看实例方法,
实例方法
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//判断元类里是否有resolveInstanceMethod方法,没有直接return
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
-
lookUpImpOrNil
判断cls的元类
有没有实现resolveInstanceMethod
,若没有实现,直接return。 -
objc_msgSend
给cls
发送一个resolveInstanceMethod
消息
3.再继续查找imp
,如果上resolveInstanceMethod
方法给imp
做了处理,那么这次就可以查到imp
了。
在LRPerson
类里面重写resolveInstanceMethod
方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s 来了",__func__);
return [super resolveInstanceMethod:sel];
}
运行代码打印结果为:
+[LRPerson resolveInstanceMethod:] 来了
+[LRPerson resolveInstanceMethod:] 来了
-[LRPerson sayHello]: unrecognized selector sent to instance 0x1011277a0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LRPerson sayHello]: unrecognized selector sent to instance 0x1011277a0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2e63fd07 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x000000010038e04a objc_exception_throw + 42
2 CoreFoundation 0x00007fff2e6bec97 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2e5a457b ___forwarding___ + 1427
4 CoreFoundation 0x00007fff2e5a3f58 _CF_forwarding_prep_0 + 120
5 KCObjc 0x0000000100000e60 main + 64
6 libdyld.dylib 0x00007fff68371cc9 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
上述打印结果,调用了两次resolveInstanceMethod
方法。我们通过断点一步步调试
1.`[person sayHello]`进入`lookUpImpOrForward`开始慢速查找
inst:
sel = "sayHello" cls = LRPerson behavior = 3
2.在类的继承链中查找`imp`,通过 `getMethodNoSuper_nolock`方法
3.没有找到`imp`,赋值`imp=forward_imp`,跳出循环进入`if (slowpath(behavior & LOOKUP_RESOLVER))`
behavior ^= LOOKUP_RESOLVER = 1
进入动态方法决议
4.进入`resolveMethod_locked`
inst:
sel = "sayHello" cls = LRPerson behavior = 1
5.LRPerson不是元类,走resolveInstanceMethod方法
6.`resolveInstanceMethod`方法
inst:
sel = "sayHello" cls = LRPerson
7.`objc_msgSend`调用一次类方法`resolveInstanceMethod` //打印第一次+[LRPerson resolveInstanceMethod:] 来了
8.`lookUpImpOrNil`查找一次`imp`,看是否在resolveInstanceMethod中做了处理
lookUpImpOrNil直接调用lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
9.进入`lookUpImpOrForward`,参数发生了改变
inst:
sel = "sayHello" cls = LRPerson behavior = 12
10. behavior = 12 会通过cache_getImp取一次缓存,取不到
然后再循环查找`getMethodNoSuper_nolock`,还是没有找到
11. behavior = 12 不满足条件,不会再执行`resolveMethod_locked`,会直接跳到`log_and_fill_cache`将`forward_imp`存进了缓存
12. behavior满足条件直接返回`nil`
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
13.回到`resolveInstanceMethod`,`imp`为`nil`,回到上一层`resolveMethod_locked`,调用`lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);`
14.再一次进入`lookUpImpOrForward`,此时的参数为
inst:
sel = "sayHello" cls = LRPerson behavior = 5
15.再一次`cache_getImp`,拿到了11步,存入的`forward_imp`,直接goto done_nolock,返回了`imp`
//forwardingTargetForSelector
16.之后会调用LRPerson的`forwardingTargetForSelector:`
17.进入`lookUpImpOrForward`
inst: <0x0>
sel = "forwardingTargetForSelector:" cls = LRPerson behavior = 14
18.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`forwardingTargetForSelector`,`imp`找不到。
19.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。
//methodSignatureForSelector
20.之后会调用LRPerson的`methodSignatureForSelector:`
21.进入`lookUpImpOrForward`
inst: <0x0>
sel = "methodSignatureForSelector:" cls = LRPerson behavior = 14
22.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`methodSignatureForSelector`,`imp`找不到。
23.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。
//class
24.重复上面的流程`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。
//sayHello
25.之后会调用LRPerson的`sayHello`
inst: <0x0>
sel = "sayHello" cls = LRPerson behavior = 2
26. `getMethodNoSuper_nolock`循环查找父类,没有找到。
27.满足`behavior & LOOKUP_RESOLVER`条件,又一次进入`resolveMethod_locked`
behavior = 0
28.`resolveInstanceMethod` -> objc_msgSend
29.又调用了一次`resolveInstanceMethod` //打印第二次+[LRPerson resolveInstanceMethod:] 来了
30. `lookUpImpOrNil`->lookUpImpOrForward
31.进入`lookUpImpOrForward`,此时的参数为
inst: <0x0>
sel = "sayHello" cls = LRPerson behavior = 12
32.`cache_getImp`找不到;`getMethodNoSuper_nolock`循环查找也找不到;
将imp=_objc_msgForward_impcache 存入缓存并返回nil
33.回到`resolveMethod_locked`方法,调用`lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);`
34.进入`lookUpImpOrForward`,此时的参数为
inst: <0x0>
sel = "sayHello" cls = LRPerson behavior = 4
35.`cache_getImp`,拿到`imp`,并`return imp`
//doesNotRecognizeSelector
36.之后会调用LRPerson的`doesNotRecognizeSelector:`
37.进入`lookUpImpOrForward`
inst: <0x0>
sel = "doesNotRecognizeSelector:" cls = LRPerson behavior = 14
38.behavior & LOOKUP_CACHE 满足条件,`cache_getImp`,因为没有实现`doesNotRecognizeSelector`,`imp`找不到。
39.`getMethodNoSuper_nolock`循环查找父类,在NSObject类中找到实现。
存入缓存,返回`imp`。
.
.
.
- 第一次是消息转发之前再给一次机会,调用一次
resolveInstanceMethod
- 第二次是转发之后,
methodSignatureForSelector
调用之后,forwardInvocation
之前,转发的类调用sayHello
也会查找imp
,也会调用一次resolveInstanceMethod
通过打印堆栈也可以清晰的查看这个流程,在图上位置打上断点:
-
第一次进来打印堆栈情况
-
第二次堆栈情况
类方法
当进来的方法是类方法的时候,走的是下面的流程
大致与实例方法一样,但是为什么在调用了
resolveClassMethod
之后,出来还要走一次resolveInstanceMethod
?
先看resolveClassMethod
源码
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//判断元类cls是否有resolveClassMethod
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//nonmeta经过处理得到的是LRPerson
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//向当前的类发送resolveClassMethod方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
与resolveInstanceMethod
基本一致,只是给一次机会的时候,改为调用类的resolveClassMethod
,然后继续在元类里查找imp
。
- 为什么元类方法会走
resolveInstanceMethod
?
类方法其实就是元类的Instance方法,我们虽然不能重写元类的代码,但是不要忘了元类的继承链。元类-> 根元类 -> NSObject
,我们可以在NSObject
中重写resolveInstanceMethod
类方法的拯救机会可以写在两个地方:
1.类的resolveClassMethod
2.NSObject的resolveInstanceMethod
resolveInstanceMethod的写法举个例子:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s 来了",__func__);
if (sel == @selector(sayHello)) {
IMP imp = class_getMethodImplementation(self, @selector(say1));
Method sayMMethod = class_getInstanceMethod(self, @selector(say1));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"%s 来了",__func__);
if (sel == @selector(sayClassMethod)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LRPerson"), @selector(say8));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LRPerson"), @selector(say8));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LRPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
消息转发
instrumentObjcMessageSends函数作用是监控objc底层消息的发送
//1.objcMsgLogEnabled 控制开关
//2.外部使用 extern
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
修改源码运行:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LRPerson * person = [LRPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
}
return 0;
}
会生成log日志,保存在/tmp/msgSends-%d
路径下
resolveInstanceMethod之后会调用一下方法:
forwardingTargetForSelector
-
methodSignatureForSelector
.
. -
doesNotRecognizeSelector
我们分析一下这些方法到底有什么作用。
forwardingTargetForSelector 快速转发
查看官方文档command+shift+0
,搜索forwardingTargetForSelector
返回一个unrecognized messages第一个指向的对象
代码验证:
//新建一个LRStudent继承于LRPerson,在子类中实现sayHello方法
@interface LRStudent : LRPerson
@end
@implementation LRStudent
- (void)sayHello {
NSLog(@"LRStudent sayHello");
}
@end
//在LRPerson.m中重写
](https://upload-images.jianshu.io/upload_images/23932582-a124cc493c3a00e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
方法,返回一个LRStudent对象
@implementation LRPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%@",NSStringFromSelector(aSelector));
return [LRStudent alloc];
}
@end
打印结果:
sayHello
LRStudent sayHello
Program ended with exit code: 0
成功的将父类的方法调用子类的实现。
methodSignatureForSelector 慢速转发
返回
selector
的NSMethodSignature
方法签名,当动态方法决议和快速转发都没有处理的时候,会走到这个方法。
此方法一般搭配
forwardInvocation:
,他们一起实现了消息的慢速转发。在LRPerson
类中添加以下方法,运行发现没有实现sayHello
也不会崩溃了。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature * m =[NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return m;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// [anInvocation invoke];
}
我们可以修改anInvocation
的target
和sel
,我们在这里可以改变消息的接受者,也可以改变调用的方法。
拓展
调用[person sayHello];
,不做任何处理。崩溃后通过lldb
调试。
-
bt
查看崩溃时的堆栈信息(从下往上看)
-
image list
当前所有镜像文件
-
前往当前文件夹找到
CoreFoundation
-
将
CoreFoundation
拖入Hopper
-
command + f
全局搜索__forwarding_prep_0___
-
点击状态栏按钮
Pseudo-code mode
查看伪代码
-
向下查找
很容易找到forwardingTargetForSelector:
,继续往下还可以methodSignatureForSelector:
和_forwardStackInvocation:
,整个调用流程清晰可见。借助反汇编工具Hopper
,更好探索方法调用过程。