通过这篇文章可以获得什么
- instrumentObjcMessageSends辅助分析方法的介绍
- 动态消息转发的一般探索思路
- 动态消息转发之快速转发
- 动态消息转发之慢速转发
- 动态消息转发之真实场景防止崩溃
- 动态消息转发之逆向探索方式
- CoreFoundation与Hopper的资源获取
- Hopper伪代码分解CoreFoundation的消息转发流程
- 消息的动态转发机制流程图
instrumentObjcMessageSends辅助分析方法的介绍
作用
可以打印出指定区域内调用的所有的方法
使用方式
// 外部引用定义
extern void instrumentObjcMessageSends(BOOL flag);
//使用区域,将想要查找的方法用instrumentObjcMessageSends圈起来
//开始监听传递参数为YES,结束监听传递参数为NO
FFPerson *person = [FFPerson alloc];
instrumentObjcMessageSends(YES);
[person likeGirls];
instrumentObjcMessageSends(NO);
复制代码
如果查看结果
此方法将指定函数开始
与结束
之间调用的函数全部以文件
的形式输出,存储路径为:\tmp\msgSends
,找到msgSends-xxxx
的文件,此文件内就是全部调用的函数
了。
查找流程图解:
源码所在位置
当objcMsgLogEnabled为true的时候,将记录这中间调用的所有的方法
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;
}
复制代码
为什么会关注到instrumentObjcMessageSends
由于消息的动态方法决议
之后所有可以看到源码的流程现都已经走完
了,那么探索也就中断
了,还好有那个骚气的男人发现了这个方法,让探索由可知的源码向未开源的源码探索......
一般探索思路
通过instrumentObjcMessageSends
开启上帝视角
快速转发
通过上面msgSends-16848
文件看到了熟悉的resolveInstanceMethod
,这个方法是动态方法决议
,看到下一个方法forwardingTargetForSelector
就变成了探究最新的线索
,既然没有源码
,那么就查看一下官方文档,在Developer Decumentation
中查找
通过此文档可以得知forwardingTargetForSelector
为快速转发流程的一个执行者
,如果使用此方法对某一个函数进行重定向
,那么将非常的有效。forwardingTargetForSelector
会返回一个对象
,成为未识别的消息的第一继承者
,即方法的重定向
。如果你想要更多的操作自由,请用forwardInvocation
进行操作。
阶段一:代码初步论证
案例代码:
@interface FFPerson : NSObject
- (void)likeGirls;
@end
@implementation FFPerson
//likeGirls方法未实现
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FFPerson *person = [FFPerson alloc];
[person likeGirls];
}
return 0;
}
复制代码
控制台打印:
2021-07-04 23:58:17.970363+0800 002-instrumentObjcMessageSends辅助分析[17126:1382603] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-04 23:58:17.972060+0800 002-instrumentObjcMessageSends辅助分析[17126:1382603] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x10055fa30
复制代码
结果是崩溃
,但是在崩溃之前打印了-[FFPerson forwardingTargetForSelector:] - likeGirls
,证明了上述的探索方向是OK的
,在此过程可以对方法进行重定向
,以防止程序crash
。
阶段二:对代码进行了进一步改动
创建一个FFBoys
的类,FFBoys.h文件中不声明任何方法
,在FFBoys.m
文件中实现likeGirls实例方法
@interface FFBoys : NSObject
//为声明任何方法
@end
@implementation FFBoys
//实现了likeGirls方法
- (void)likeGirls {
NSLog(@"%s",__func__);
}
@end
@interface FFPerson : NSObject
//声明了likeGirls实例方法
- (void)likeGirls;
@end
@implementation FFPerson
//并未实现likeGirls实例方法
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//此刻将FFPerson的实例方法likeGirls发送到了FFBoys,让FFBoys来实现
return [FFBoys alloc];
}
@end
复制代码
控制台打印结果:
2021-07-05 00:07:17.270104+0800 002-instrumentObjcMessageSends辅助分析[17170:1388173] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:07:17.270736+0800 002-instrumentObjcMessageSends辅助分析[17170:1388173] -[FFBoys likeGirls]
复制代码
可以很清晰的得知FFPerson
的实例方法likeGirls
由FFBoys
实现了。
疑问点一:为什么好好的方法,不在FFPerson类中自己实现,还要启用快速转发,由FFBoys来实现呢?
阶段三:定制一个背锅侠,动态添加新方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
复制代码
慢速转发
当FFBoys
这个背锅侠
不想背锅的时候,他也不想实现
这个方法,那么此时就会进入到慢速转发了
。也就是msgSends-16848
的methodSignatureForSelector
方法,使用相同的方式
,那么就查看一下官方文档,在Developer Decumentation中
查找:
给定一个sel
,返回一个方法的签名
,跟forwardInvocation
关联使用,那么就查看一下官方文档,在Developer Decumentation中
查找:
阶段一:methodSignatureForSelector
案例代码
@implementation FFPerson
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
@end
复制代码
控制台打印结果:
2021-07-05 00:42:17.146674+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:42:17.147110+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson methodSignatureForSelector:] - likeGirls
2021-07-05 00:42:17.147231+0800 002-instrumentObjcMessageSends辅助分析[17283:1407758] -[FFPerson likeGirls]: unrecognized selector sent to instance 0x103851710
复制代码
结果依然是崩溃
,这里已经打印了methodSignatureForSelector
,由于并未返回签名
,并且未配合forwardInvocation
方法使用。
阶段二:forwardInvocation
案例代码:
@implementation FFPerson
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
FFBoys * boys = [FFBoys alloc];
//动态插入方法
return [super forwardingTargetForSelector:aSelector];
}
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(likeGirls)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
}
@end
复制代码
我这里通过判断sel
是否是likeGirls
,并为其添加了方法签名
,然后添加了forwardInvocation
方法,但是并未实现任何事务
,这里不会崩溃
事务的概念
关于事务,methodSignatureForSelector
方法通过创建并返回一个方法签名
之后,进入forwardInvocation
,这个方法就是用来处理事务
的,当苹果一系列的转发流程
来到这里,通过方法签名
生成了一个事务,那么这个事务是可做可不做的
,会保存anInvocation
。比如当你想起我的时候,你问我,在吗,我会回答你,我很好,一直在等你。
代码验证:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
复制代码
控制台打印
2021-07-05 00:56:28.493284+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] -[FFPerson forwardingTargetForSelector:] - likeGirls
2021-07-05 00:56:28.493856+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] -[FFPerson methodSignatureForSelector:] - likeGirls
2021-07-05 00:56:28.494254+0800 002-instrumentObjcMessageSends辅助分析[17350:1416427] <FFPerson: 0x104016a30> - likeGirls
复制代码
通过打印证明了likeGirls
这个事务的存在,只要你想我在的时候我一直都在
,这就是forwardInvocation
的涵义,当你不想找到我
的时候,当前forwardInvocation
就会当作什么都没发生过
。此事务就会被流失
,此为慢速转发流程
。
真实场景防止崩溃
将慢速转发流程methodSignatureForSelector
+ forwardInvocation
,放在NSObject的分类
中实现,那么程序就不会因为找不到方法而崩溃
了。只是假象的消失
了,问题还是真实存在
的,并且对内存
、资源
造成了更多的浪费
,意味着在这个流程中必然经历了很多的多余的方法和不必要的流程
。
慢速转发注意点:
要响应您的对象本身无法识别的方法,除了 forwardInvocation: 之外,您还必须覆盖 methodSignatureForSelector:。 转发消息的机制使用从 methodSignatureForSelector: 获得的信息来创建要转发的 NSInvocation 对象。 您的覆盖方法必须为给定的选择器提供适当的方法签名,或者通过预先制定一个或通过向另一个对象询问一个。
forwardInvocation完成代码
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
FFBoys *boys = [FFBoys alloc];
//如果自己可以处理,直接调用
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invoke];
}
//判断FFBoys是否可以处理,如果可以,直接调用
else if ([boys respondsToSelector:anInvocation.selector]){
[anInvocation invokeWithTarget:boys];
}
//没有任何人去实现,那么这里将错误上报
else {
NSLog(@"发生了错误的方法:%@ - %@",anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
}
复制代码
逆向探索方式
通过lldb
动态调试指令bt
,打印当前的堆栈
:
这里可以得知调用likeGirls
依然是崩溃
的,但是通过堆栈信息
可以找打崩溃前都调用了那些函数
,doesNotRecognizeSelector
,此个函数时当快速查找
和慢速查找
都失效的时候会调用的方法,是属于coreFoundation
框架下的,同时此方法的上两个方法
同时也是coreFoundation库内的函数,不由得感觉到有鬼,有可能是线索
。
找Apple的CoreFoundation源码
[Souece Browser地址:opensource.apple.com/tarballs/CF…]
找到这里,我觉得我又行了
,又站起来了
。开始搞CoreFoundation
源码
源码搜索:___forwarding___
、forwarding
源码搜索:_CF_forwarding_prep_0
经过一阵瞎逼操作
,啥也没找到,最后得出结论CoreFoundation
并没有完全的开源
,Apple爸爸还是你爸爸。哎~不给你看。
去系统里面偷一个CoreFoundation反汇编调试
CoreFoundation动态库资源获取
Hopper Disassembler反汇编工具介绍
官网地址:https://www.hopperapp.com/
基础功能介绍
探索开始,搜索_CF_forwarding_prep_0与___forwarding___
这里已经在对动态库CoreFoundation
的反汇编
中发现了_CF_forwarding_prep_0
,___forwarding___
,并且发现了调用顺序
跟报错的堆栈式一致
的。但是看反汇编的难度比较大
,Hopper给我们提供一个更好的方式,通过伪代码
的方式分析一下___forwarding___
都做了什么?
Hopper伪代码分解CoreFoundation的消息转发流程
自此,消息的快速转发与慢速转发整体流程已经探索完成。
消息的动态转发机制流程图
解答在源码探索过程中提出的疑问点:
为什么好好的方法,不在FFPerson类中自己实现,还要启用快速转发,由FFBoys来实现呢?
方法的查找过程中,经过前面一些列的过程,到动态方法决议
,这里方法找不到也没有关系
,再到forwardingTargetForSelector
,你告诉我谁有这个方法
,谁有相同的方法
,即找一个背锅侠
。
慢速转发流程与快速转发流程的区别是什么?
慢速转发流程
相对于快速转发流程
,对于开发者来讲更加的自由
、灵活
的感觉。开发者只要提供一个签名
,这个签名可以通过方法直接得到的,接下来通过forwardInvocation
可以想处理也可以
不处理`。