方法查找流程
【第一步】 方法查找流程-快速查找流程
【第二步】 方法查找流程-慢速查找
【第三步】 动态决议
【第四步】 消息转发
动态决议
在方法查找流程-慢速查找中,探索到如果通过慢速查找都没有找到,则会进行动态方法决议
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
resolveMethod_locked源码
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 如果没有找到就会调用
resolveMethod_locked(inst, sel, cls, behavior)
- 判断
cls
是否是元类-
cls
是元类- 执行
resolveClassMethod(inst, sel, cls)
,使用类方法的动态方法决议 -
lookUpImpOrNilTryCache(inst, sel, cls)
,类方法解析是否找到或者为空- 如果没有找到或者为空,则执行
resolveInstanceMethod(inst, sel, cls)
,
- 如果没有找到或者为空,则执行
- 执行
-
cls
不是元类- 执行
resolveInstanceMethod(inst, sel, cls)
,使用对象方法的动态方法决议
- 执行
-
解决方法没有实现的思路一
通过上面的分析,我们知道如果没有找到方法的实现,会进行动态方法决议,那我们可以尝试针对类方法没有实现我们可以去实现resolveClassMethod
,对象方法没有实现我们可以去实现resolveInstanceMethod
。
我们声明一个类,分别定义一个类方法和对象方法,不去实现它
+ (void)sayHello;
- (void)sayNB;
resolveClassMethod的实现
我们直接调用没有实现的类方法sayHello
,运行发生崩溃,控制台打印结果如下:
+[Person sayHello]: unrecognized selector sent to class 0x100008208
解决方法:
+ (void)resolveClassMethodNotFound{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayHello)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(resolveClassMethodNotFound));
Method solveClassMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(resolveClassMethodNotFound));
const char *type = method_getTypeEncoding(solveClassMethod);
return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
再次运行的结果
sayHello 来了
+[Person resolveClassMethodNotFound]
分析
通过上面的代码,确实在慢速查找没有找打方法的实现的时候,类方法会来到resolveClassMethod
,此时,我们实现resolveClassMethod
,就能防止崩溃,这里的做法是把当前的方法,动态的添加一个类方法,并将sayHello
的实现转移给新添加的方法
resolveInstanceMethod的实现
我们直接调用sayNB
对象方法,会报方法找不到的报错如下:
-[Person sayNB]: unrecognized selector sent to instance 0x100442da0
会提示实例方法,找不到。unrecognized selector sent to instance
解决方案:
- (void)resolveinstanceMethodNotFound{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(resolveinstanceMethodNotFound));
Method solveMethod = class_getInstanceMethod(self, @selector(resolveinstanceMethodNotFound));
const char *type = method_getTypeEncoding(solveMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
这里没有找到
sayNB
的方法实现,来到resolveInstanceMethod
,只需动态的添加一个方法,即可视做完成了sayNB
的实现。这里的做法是获取resolveinstanceMethodNotFound
的imp
,和签名。绑定到sayNB
的sel
,我们知道方法的存储是通过sel
&_mask
来获取得到存储下标,这样我们在调用sayNB
的时候,默认调用的就是resolveinstanceMethodNotFound
的实现。
修改之后的结果
sayNB 来了
-[Person resolveinstanceMethodNotFound]
在resolveMethod_locked
的实现中,我们看到如下代码
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
对于类方法,再执行了
resolveClassMethod
之后,会尝试lookUpImpOrNilTryCache
,从缓存中去找,如果没有找到,也会执行resolveInstanceMethod
实例方法的动态决议,是不是意味着我们不用实现resolveClassMethod
只实现一个resolveInstanceMethod
就行了。因为元类没有实体类,沿着继承链我们只能找到NSObject
,所以我们给NSObject添加一分类,并实现resolveInstanceMethod
,来验证一下:
处理方案
- 在NSObject+SolveInstance.h中添加如下代码,
- 因为NSObject是根类,super为nil,所有我们这里直接返回NO。
- 我们只处理明确知道的
sayNB
和sayHello
两个没有实现的方法,避免产生不必要的崩溃 - 不管是
类方法
,还是实例方法
,找到NSObject
都会变成实例方法
,所以这里返回转移的方法实现定义为实例方法
就行了
- (void)resolveinstanceMethodNotFound{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayHello) || sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(resolveinstanceMethodNotFound));
Method solveMethod = class_getInstanceMethod(self, @selector(resolveinstanceMethodNotFound));
const char *type = method_getTypeEncoding(solveMethod);
return class_addMethod(self, sel, imp, type);
}
return NO;
}
跟我们预想的一样,打印结果如下
sayHello 来了
-[NSObject(SolveInstance) resolveinstanceMethod\M-LotFound]
sayNB 来了
-[NSObject(SolveInstance) resolveinstanceMethod\M-LotFound]
充分印证了:
消息转发
打开消息发送的日志
我们跟log_and_fill_cache
的源码的时候发现,消息的日志存储在/tmp/msgSends
但是我们在这个目录下,并没有找到消息发送的日志,缺少了条件
objcMsgLogFD == (-1)
,打开这个条件的方式如下:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [Person alloc];
instrumentObjcMessageSends(YES);
[person sayNB];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
这时我们就能拿到sayNB
调用之后的消息日志了,不做任何崩溃处理,直接运行,在/tmp/msgSends
目录下就获取到了消息日志
通过日志我们看到,再执行动态方法决议之后,还执行了
forwardingTargetForSelector
和methodSignatureForSelector
。通过文档,我们还知道
methodSignatureForSelector
需要和forwardInvocation
搭配起来使用。接下来开始验证
消息转发-快速转发
forwardingTargetForSelector
在Person的.m文件中实现如下代码
实例方法-消息转发(快速转发)
/// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [Student alloc];
}
// Student.m中的实现
- (void)sayNB {
NSLog(@"%s", __func__);
}
打印结果如下
2021-01-18 15:38:14.107644+0800 消息转发流程[16585:199229] -[Person forwardingTargetForSelector:]- sayNB
2021-01-18 15:38:14.109524+0800 消息转发流程[16585:199229] -[Student sayNB]
实例方法可以转交给类方法么?代码如下
/// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [Student class];
}
// Student.m中的实现
+ (void)sayNB {
NSLog(@"%s", __func__);
}
打印如下:
2021-01-18 15:39:12.076271+0800 消息转发流程[16606:199968] -[Person forwardingTargetForSelector:]- sayNB
2021-01-18 15:39:12.077819+0800 消息转发流程[16606:199968] +[Student sayNB]
小结
对于实例方法,快速转发需要重写实例方法forwardingTargetForSelector
,
如果转发的是一个
类
,那么转发的类必须实现该消息的类方法实现
如果是个
对象
,则需要实现该方法的实例方法实现
如果以上不对应
,或者没有实现
,则会报错
类方法-消息转发(快速转发)
/// 快速转发
+ (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [Teacher class];
}
// Teacher.m的方法实现
+ (void)sayHello{
NSLog(@"%s", __func__);
}
运行结果
2021-01-18 15:24:28.039137+0800 消息转发流程[16383:193004] +[Person forwardingTargetForSelector:]- sayHello
2021-01-18 15:24:28.040839+0800 消息转发流程[16383:193004] +[Teacher sayHello]
还可以有另外一种方式,代码如下
+ (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [Teacher alloc];
}
// Teacher.m的方法实现
- (void)sayHello{
NSLog(@"%s", __func__);
}
运行结果
2021-01-18 15:31:07.617114+0800 消息转发流程[16474:195831] +[Person forwardingTargetForSelector:]- sayHello
2021-01-18 15:31:07.619068+0800 消息转发流程[16474:195831] -[Teacher sayHello]
小结
对于类方法,快速转发需要重写类方法forwardingTargetForSelector
,
- 如果转发的是一个
类
,那么转发的类必须实现该消息的类方法实现
- 如果是个
对象
,则需要实现该方法的实例方法实现
如果以上不对应
,或者没有实现
,则会报错
快速转发总结
- 类方法和实例方法有自己对应的
forwardingTargetForSelector
实现,需要重写对应的forwardingTargetForSelector
方法 - 当前
类或对象
处理不了,可以交给其它类或者对象
来处理。 - 如果转交的是
类
,则需要实现同名类方法
- 如果转交的是
对象
,则需要实现同名的实例方法
- 如果其它
类或对象
没有处理或上面两个规则不对应
,就会报错,所转发的类没有找打改方法的实现
消息转发-慢速转发
methodSignatureForSelector
forwardInvocation
在Person.m 文件中写下如下代码
/// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s- %@", __func__, anInvocation);
}
运行,发现不报错,运行结果如下
2021-01-18 16:43:19.517884+0800 消息转发流程[17265:220490] -[Person methodSignatureForSelector:]- sayNB
2021-01-18 16:43:19.520205+0800 消息转发流程[17265:220490] -[Person forwardInvocation:]- <NSInvocation: 0x1004078a0>
在forwardInvocation
中加上如下代码
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s- %@", __func__, anInvocation);
[anInvocation invokeWithTarget:[Student alloc]];
}
打印结果
2021-01-18 16:46:21.053838+0800 消息转发流程[17325:222827] -[Person methodSignatureForSelector:]- sayNB
2021-01-18 16:46:21.059838+0800 消息转发流程[17325:222827] -[Person forwardInvocation:]- <NSInvocation: 0x100606dd0>
2021-01-18 16:46:21.061812+0800 消息转发流程[17325:222827] -[Student sayNB]
同样anInvocation
的target
可以是一个类,当需要实现同名的类方法
总结
-
methodSignatureForSelector
需要搭配forwardInvocation
使用,forwardInvocation
系统会自动调用。 -
forwardInvocation
可以不处理anInvocation
事物,不会引发崩溃 -
anInvocation
的target
可以是类
,也可以是实例对象
,若指定了类
或者对象
,必须实现对应的同名类方法或实例方法
我们将,动态方法决议,消息转发-快速转发,消息转发-慢速转发都放开,只在消息转发-慢速转发中处理,代码如下
/// 动态方法决议
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@",__func__,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
/// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
/// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s- %@", __func__, anInvocation);
[anInvocation invokeWithTarget:[Student alloc]];
}
得出如下结果:
2021-01-18 17:27:22.544241+0800 消息转发流程[17843:242496] +[Person resolveInstanceMethod:] - sayNB
2021-01-18 17:27:22.546082+0800 消息转发流程[17843:242496] -[Person forwardingTargetForSelector:] - sayNB
2021-01-18 17:27:22.546903+0800 消息转发流程[17843:242496] -[Person methodSignatureForSelector:]- sayNB
2021-01-18 17:27:22.547638+0800 消息转发流程[17843:242496] +[Person resolveInstanceMethod:] - _forwardStackInvocation:
2021-01-18 17:27:22.549293+0800 消息转发流程[17843:242496] -[Person forwardInvocation:]- <NSInvocation: 0x100604450>
2021-01-18 17:27:22.549812+0800 消息转发流程[17843:242496] -[Student sayNB]
我们再去消息日志里面查看,得到的调用顺序同样是如上面所示。
总结
- 动态方法决议,在整个环节会被调用两次
【第一次】在慢速查找没有查找的进入动态方法决议的时候是拯救查找的方法,
【第二次】在慢速转发过程中调用是拯救_forwardStackInvocation:
,可以直接在resolveInstanceMethod
处理 - 需要注意转发的是
类
还是对象
,做出对应的实现。