前言
在上一篇动态方法决议中,我们讲到消息慢速查找没找到会进入动态决议
。如果动态决议没有处理,通过日志发现了新的东西forwardingTargetForSelector
和methodSignatureForSelector
,也就是消息查找的最后流程 消息转发
。
消息转发
- 消息转发分为
快速转发
和慢速转发
,先来看看快速转发。
快速转发
快速转发也就是forwardingTargetForSelector
,这个方法有什么作用呢,先来在objc-818.2源码
中搜索,发现只提到了方法,没有找到具体使用。再来找下方法文档Organizer
-> cmd + shift + 0
,然后搜索forwardingTargetForSelector
forwardingTargetForSelector
- 文档对这个方法的解释是:如果方法执行不了,可以给别人,也就是找个人来执行的意思
代码验证
- 下面在
脱离源码环境
来验证下
// .h
@interface WSPerson : NSObject
- (void)sayLike;
@end
// .m
@implementation WSPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
// 调用
WSPerson *person = [[WSPerson alloc] init];
[person sayLike];
@end
-
执行结果如下:
-
说明找不到方法会走到这里,文档里说,方法找不到,可以指定一个来执行,我们在
WSTeacher
里添加一个sayLike
方法,并实现,然后将forwardingTargetForSelector
的返回值,指定为WSTeacher对象
:
// WSTeacher.h
@interface WSTeacher : NSObject
- (void)sayLike;
@end
// WSTeacher.m
@implementation WSTeacher
- (void)sayLike {
NSLog(@"🎉🎉🎉 %s", __func__);
}
@end
// WSPerson.m
@implementation WSPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));
return [WSTeacher alloc];
}
@end
运行结果如下:
- 这样就在
WSTeacher
类中找到了,不过这个过程有一个弊端,如果指定的这个类也没有实现这个方法
怎么破?接下来就从快速转发进入慢速转发
。
慢速转发
在辅助日志msgSends
中,我们能够看到,当forwardingTargetForSelector
后面还有个methodSignatureForSelector
方法,也就是快速转发没找到,就会进入methodSignatureForSelector
方法
methodSignatureForSelector
- 先来看看
methodSignatureForSelector
说明: - 文档上说,这个方法要返回一个方法签名
NSMethodSignature
,而且在消息的转发期间,必须要创建NSInvocation
,也就是实现forwardInvocation
方法
代码验证
- 先验证下代码是否能进来,首先要去掉
WSTeacher
中的sayLike
实现:
@implementation WSPerson
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"🎈🎈🎈 %s - %@", __func__, NSStringFromSelector(aSelector));
return [WSTeacher alloc];
}
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"🍊🍊🍊 %s - %@", __func__, NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
@end
- 再来运行下看看
疑问:
methodSignatureForSelector
居然没走,这是为什么?
- 再来分析下这段代码,发现
forwardingTargetForSelector
指定了WSTeacher
去执行查找,但WSTeacher
中并没有实现这些,所以就不会走,可以去掉快速转发
再尝试:
代码果然和预期一样走进来了,在文档上说,需要返回
方法签名
和实现forwardInvocation
方法,我们修改下这里面代码:
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"🍊🍊🍊 %s - %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayLike)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
运行结果如下:
这样就
避免了崩溃
,forwardInvocation
中保存了方法签名信息。NSInvocation
也可以处理相关的事务,例如指定实现
:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
WSTeacher *t = [WSTeacher alloc];
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invoke];
} else if ([t respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:t];
} else {
anInvocation.target = t;
anInvocation.selector = @selector(sayLearn);
[anInvocation invoke];
NSLog(@" 方法找不到 ");
}
}
forwardInvocation
可以指定给某个类去调用
,也可以改变自己的target,和selector
等forwardInvocation
流程相当于它告诉程序sayLike
这个方法还在,程序就不会去做异常处理,但实际是存在的,也可以自主的做去处理。不过这一系列的流程下来,会造成资源的浪费。
总结
- 快速转发:调用
forwardingTargetForSelector
实现快速转发流程,可以返回自己想要替自己实现的对象去实现该方法,如果返回nil
将进入慢速转发
流程。 - 慢速转发:慢速转发需要调用
methodSignatureForSelector
和forwardInvocation
共同实现,methodSignatureForSelector
需要返回方法签名,而forwardInvocation
里面可以做一些处理,例如指定对象处理,指定方法等。如果 没有方法签名,则结束慢速查找。
消息转发图解
查看堆栈
- 假如这些转发流程都没有处理,运行肯定是报错的,那么怎么查看呢,我们可以用
bt命令
打印堆栈:
通过分析得知,在doesNotRecognizeSelector
之后还走两个方法___forwarding___
和__forwarding_prep_0___
方法,这两个方法都在CoreFoundation
库中,按照以往的经验去源码中分析下作用,但是源码中并没有找到
,说明CoreFoundation
库并没有完全开源,那怎么搞?我们可以去查看下动态库。
- 先通过
image list
命令找到动态库路径,然后找到CoreFoundation
动态库
- 然后使用
Hopper
或者IDA
去分析,本文主要在Hopper
中分析
反汇编(Hopper)
分析
将找到的CoreFoundation
动态库拖到Hopper
中
- 然后点击左侧
label
搜索__forwarding_prep_0___
,主要查看伪代码
- 我们看到
__forwarding_prep_0___
中调用___forwarding___
后得到rax
,双击进入___forwarding___
方法:
- 这里显示判断
forwardingTargetForSelector
有没有实现,如果没有实现就会走loc_64ad7
,也就是慢速转发:
- 在慢速转发时,先判断是不是僵尸对象
_NSZombie_
,再判断methodSignatureForSelector
是否实现,没有实现就走loc_64e47
- 如果
methodSignatureForSelector
返回值为nil
,则走loc_64eac
,也就是报错 - 如果
methodSignatureForSelector
有返回值,则会去判断forwardStackInvocation
方法是否存在,然后进行后面的处理
总结
hopper
也可以查看流程,只不过流程看起来没那么舒服要慢慢的去分析,需要慢慢的去分析,最后还是能够分析处理转发流程。