前言
在《Objective-C 动态方法决议》一文中已经知道了动态方法协议的实现可以在resolveInstanceMethod
方法中,但是resolveInstanceMethod
方法之后就没有流程要走了嘛?其不然,立刻进行探索!
准备资料
- objc4-818.2 源码
- CF 源码
- 反汇编工具
Hopper
和ida
(ida最好是windows
版本的)
消息转发
消息发送在经过动态方法决议仍然没有查找到正真的方法实现,此时动态方法决议抛出imp = forward_imp
进入消息转发
流程。转发流程分两步快速转
发和慢速转发
。
日志辅助查看resolveInstanceMethod之后做了什么
通过lookUpImpOrForward
--> log_and_fill_cache
--> logMessageSend
,进入logMessageSend
看到源码的实现:
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
logMessageSend
的内部实现这里就不详细说明了,有兴趣的同学可以查看源码,里面主要是日志保存路径的设置以及一些相关内容的设定。通过保存的路径就可以查看输出的日志。
日志输出代码实现
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
XJLPerson * person = [XJLPerson alloc];
instrumentObjcMessageSends(YES);
[person sayLost];
instrumentObjcMessageSends(NO);
}
return 0;
}
输出结果:
- 在动态决议方法之后的消息转发流程有
forwardingTargetForSelector
和methodSignatureForSelector
消息转发流程。
快速转发 --- forwardingTargetForSelector
forwardingTargetForSelector
方法是不是感觉熟悉又陌生呢?那么我们通过文档看看他的描述是怎样子的。打开Xcode
,command + shift +0
,然后全局搜索forwardingTargetForSelector
,得到以下描述:
forwardingTargetForSelector
含义是返回未识别消息
重定向的对象,简单理解指定一个对象,让这个对象去接收这个消息。
实例探究快速转发
先定义两个类,分别是XJLPerson
和LGPerson
,在XJLPerson
中声明sayLost
方法,但是不实现此方法,但是在LGPerson
中实现sayLost
方法。注意:XJLPerson
与LGPerson
可以不是继承关系
。
@implementation XJLPerson
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(sayLost)) {
return [[LWTest alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@implementation LGPerson
-(void)sayHello{
NSLog(@"---%s---",__func__);
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
XJLPerson * person = [XJLPerson alloc];
[person sayLost];
}
return 0;
}
打印结果:
2021-07-16 15:46:32.267031+0800 KCObjcBuild[33863:595901] ----[LGPerson sayLost]---
由打印结果可以得出,XJLPerson
与LGPerson
两者是没有继承关系
的,但是我们可以在forwardingTargetForSelector
方法里面给XLJPerson
指定实现的类(LGPerson
)。此时sayLost
方法仍然可以查询并实现,没有发生崩溃
的情况。其实消息在查询过程中先去跟它关系近的类中
去查找,最后没找到。于是系统把这个权限丢给开发者,你告诉我哪个对象和类能接收这个消息,这就是快速转发
流程。
如果不给指定的类实现,快速转发也不行了,系统没有底线的给你进行慢速转发,这就有点不合理了吧。
慢速转发 -- methodSignatureForSelector
同理,我们先查看methodSignatureForSelector方法的官方描述,得到如下:
-
methodSignatureForSelector
算是消息发送的最后
一个流程了,在此方法到来之前已经允许开发者进行动态方法决议
和快速转发
的处理,如果这两次机会都不珍惜的话,最后只能给你个报错信息
咯。 -
methodSignatureForSelector
的含义是返回一个NSMethodSignature
对象,该对象包含由给定选择器标识的方法的描述。 -
methodSignatureForSelector
一般搭配和forwardInvocation
使用,如果methodSignatureForSelector
方法返回的是一个nil
就不会调用forwardInvocation
。
实例探究快速转发
@implementation XJLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---",anInvocation);
}
@end
打印结果:
2021-07-16 16:12:00.760327+0800 KCObjcBuild[34202:602049] -[XJLPerson sayLost]: unrecognized selector sent to instance 0x100663ba0
此时methodSignatureForSelector
的返回值是nil
,慢速转发完成,直接奔溃!!
@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(sayLost)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@end
打印结果:
2021-07-16 16:16:42.540913+0800 KCObjcBuild[34317:604754] ---<XJLPerson: 0x10065d500>---sayLost---
如果methodSignatureForSelector
的返回值是NSMethodSignature
对象,则会调用forwardInvocation
进行实物处理anInvocation
保存了NSMethodSignature
签名信息,还有目标方法的方法签名sel
,以及方法的接收者
。此时不会报奔溃信息,当然也可以处理anInvocaion
事务。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
LGPerson* person = [LGPerson alloc];
anInvocation.target = person;
anInvocation.selector = @selector(sayNB);
[anInvocation invoke];
}
打印结果:
2021-07-16 16:21:34.728551+0800 KCObjcBuild[34432:606907] -[LGPerson sayNB]
-
anInvocation
的target
是[XJLPerson alloc],anInvocation
的selector
是@selector(sayNB)
,[anInvocation invoke]
触发消息的调用。 -
forwardInvocation
方法就像一个不能识别的消息的分发中心
,它能够将一个消息翻译
成另外一个消息,或者简单的"吃掉“
某些消息。所以不处理也不会崩溃。
消息换发总结
- 快速转发:通过
forwardingTargetForSelector
实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil
,进入慢速转
发流程。 - 慢速转发:通过
methodSignatureForSelector
和forwardInvocation
共同实现,如果methodSignatureForSelector
返回值是nil
,慢速查找流程结束,如果返回值不为nil,forwardInvocation
的事务处理不处理都不会崩溃
。 - 可以在
forwardInvocation
方法中重新设定target
和selector
来进行处理。
消息转发流程图
反汇编探究
- 方法崩溃之后会继续调用
__forwarding_prep_0__
方法。 -
__forwarding_prep_0__
是属于coreFoundation
系统库的。 -
__forwarding_prep_0__
方法走完之后会进入doesNotRecognizeSelector
方法。(下面反汇编会引出)
下个符号断点看看汇编的情况
下载
CoreFoundation
库以后的代码,在源码中全局搜索并无发现,说明这块内容苹果并没有对外提供。
Hopper或者IDA反汇编
Hopper
反汇编的工具,我们通过反汇编CoreFoundation
的可执行文件,去查找__forwarding_prep_0___
,CoreFoundation
的可执行文件怎么获取的呢?可以在lldb
调试中使用image list
查看系统库加载,然后每个库都有对应的路径,根据路径找到coreFoundation
如下:
拖入coreFoundation的执行文件到Hopper,并查询forwarding_prep_0_
发现我们无法查找
__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
根据签名信息处理相关事务。
总结
消息的转发探究就到这了,学习过程中收获满满哦。加油!