前言
IOS底层原理之动态方法决议中探究了动态方法决议。
在动态决议之后,通过日志辅助功能认识到forwardingTargetForSelector和
methodSignatureForSelector方法,也就是消息发送的最后一个流程消息转发。
准备工作
- objc4-818.2 源码
- CF 源码
- 反汇编工具Hopper和ida
消息转发
消息发送在经过动态方法决议仍然没有查找到正真的方法实现,此时动态方法决议抛出imp = forward_imp进入消息转发流程。转发流程分两步快速转发和慢速转发
快速转发
forwardingTargetForSelector
通过日志辅助功能认识到forwardingTargetForSelector,但是这个方法具体的含义是一无所知的,下面来认识下这个方法。打开Xcode,command shift 0,然后全局搜索forwardingTargetForSelector
forwardingTargetForSelector含义是返回未识别消息重定向的对象,简单理解指定一个对象,让这个对象去接收这个消息
实例探究快速转发
首先定义LWPerson类和LWTest类,然后在main函数中调用sayHello方法。
LWPerson没有实现sayHello方法,LWTest类实现sayHello方法,LWTest类和LWPerson类可以不是继承关系
代码语言:javascript复制@implementation LWPerson
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(sayHello)) {
return [[LWTest alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
(滑动显示更多)
代码语言:javascript复制@implementation LWTest
-(void)sayHello{
NSLog(@"---%s---",__func__);
}
@end
(滑动显示更多)
代码语言:javascript复制int main(int argc, char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
[p sayHello];
}
return 0;
}
(滑动显示更多)
代码语言:javascript复制2021-07-05 17:21:58.201416 0800 objc_msgSend[23131:807070] ----[LWTest sayHello]---
(滑动显示更多)
打印结果显示:LWTest类和LWPerson类没有任何关系,但是指定给LWTest类,仍然最后可以查询到,并且没有崩溃消息,其实消息在查询过程中先去跟它关系近的类中去查找,最后没找到。
于是系统把这个权限丢给开发者,你告诉我哪个对象和类能接收这个消息,破罐子破摔了,这我没想到的啊。
如果不给指定的类实现,快速转发也不行了,系统没有底线的给你进行慢速转发,就离谱
慢速转发
慢速转发methodSignatureForSelector也是消息查找的最后一个流程。给了动态方法决议,给了快速转发,在给你次慢速转发的机会。
俗话说事不过三,太过分了系统也忍不了,只能给你个崩溃耗子尾汁吧
methodSignatureForSelector的含义是返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。
methodSignatureForSelector一般搭配和forwardInvocation使用,
如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation
代码语言:javascript复制@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---",anInvocation);
}
@end
(滑动显示更多)
代码语言:javascript复制2021-07-05 21:09:59.688392 0800 objc_msgSend[1121:21900] -[LWPerson sayHello]:
unrecognized selector sent to instance 0x60000001c000
(滑动显示更多)
此时methodSignatureForSelector的返回值是nil,慢速转发完成,直接奔溃
代码语言:javascript复制@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@end
(滑动显示更多)
代码语言:javascript复制2021-07-05 21:42:47.001759 0800 objc_msgSend[1629:44959] ---<LWPerson: 0x60000000c140>---sayHello
(滑动显示更多)
如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation进行实物处理。
anInvocation保存了NSMethodSignature签名信息,还有目标方法的方法签名sel,以及方法的接收者。
此时不会报奔溃信息,当然也可以处理anInvocaion事务。
代码语言:javascript复制- (void)forwardInvocation:(NSInvocation *)anInvocation{
LWTest * test = [LWTest alloc];
anInvocation.target = test;
anInvocation.selector = @selector(sayBeautiful);
[anInvocation invoke];
}
(滑动显示更多)
代码语言:javascript复制2021-07-05 21:52:29.117134 0800 objc_msgSend[1787:51943] ----[LWTest sayBeautiful]---
(滑动显示更多)
anInvocation的target是[LWTest alloc],anInvocation的selector是@selector(sayBeautiful),[anInvocation invoke]触发消息的调用。
forwardInvocation方法就像一个不能识别的消息的分发中心,它能够将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息。所以不处理也不会崩溃
消息换发总结
- 快速转发:通过forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程
- 慢速转发:通过methodSignatureForSelector和forwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束,如果有返回值forwardInvocation的事务处理不处理都不会崩溃
消息转发流程图
反汇编
方法调用奔溃,堆栈信息显示从__forwarding_prep_0___ 往下调用最后又调用了doesNotRecognizeSelector。具体的流程探究下
__forwarding_prep_0___是属于CoreFoundation系统库的
下载CoreFoundation库以后的代码,在源码中全局搜索并无发现,说明这块内容苹果并没有对外提供
Hopper或者ida探索
Hopper反汇编的工具,我们通过反汇编CoreFoundation的可执行文件,
去查找__forwarding_prep_0___,CoreFoundation的可执行文件怎么获取
image list可以获取所有的镜像文件列表,查找到CoreFoundation的文件路径
全局搜索__forwarding_prep_0___,发现只有一个,且会调用__forwarding__,进入__forwarding__方法
- 如果forwardingTargetForSelector方法没有实现,跳转 loc_64ad7流程
- 如果forwardingTargetForSelector方法的返回值是nil,跳转 loc_64ad7流程
此时进入了慢速转发流程
- 如果methodSignatureForSelector没有实现直接跳转到loc_64e47流程
- 如果methodSignatureForSelector返回值等于nil跳转到loc_64eac流程
- 如果methodSignatureForSelector返回了签名信息的对象
- loc_64e47流程:直接报错跳转到loc_64eac流程
- loc_64eac流程:doesNotRecognizeSelector崩溃处理
返回签名信息forwardInvocation方法处理事务
总结
方法的探究基本已经结束,通过探究方法体会到整个探究的思路和方式,这个在我看来是最重要的