前言
上一篇 OC 原理探索:动态方法决议 中我们进行了动态方法决议
的探索,但是如果还是没有处理呢,后面还会做什么吗,今天来探索动态方法决议
后面的流程。
准备工作
- objc4-818.2 源码
- CF 源码
一、消息转发流程引入
我们先进入lookUpImpOrForward
函数,定位到动态方法决议
处的代码。
- 可以看到
动态方法决议
后面已经没有代码了,剩下的只是goto
的代码和return
相关了。 - 流程已经走完了,接下来该怎么探索呢,我们发现有个
log_and_fill_cache
函数,点击进去看一下。
instrumentObjcMessageSends 函数引入
log_and_fill_cache
函数中又调用了logMessageSend
:
- 我们可以看到
logMessageSend
函数中打印了一些+
、-
方法和地址,还有/tmp/msgSends-
这种类似沙盒目录相关的操作。 - 但是在
log_and_fill_cache
函数中可以看到,只有objcMsgLogEnabled
为true
时才能调用logMessageSend
,那么探索一下什么时候objcMsgLogEnabled
为true
。
全局搜索objcMsgLogEnabled
:
- 经过搜索,我们发现只有这一个地方可以让
objcMsgLogEnabled
的值为true
,所以接下来我们用extern
修饰instrumentObjcMessageSends
,让外部可以访问。
instrumentObjcMessageSends 函数使用
在项目中添加下面的代码,运行程序:
@interface SSLPerson : NSObject
- (void)say1;
@end
@implementation SSLPerson
@end
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
instrumentObjcMessageSends(YES);
[person say1];
instrumentObjcMessageSends(NO);
}
return 0;
}
复制代码
- 工程依旧会报错,我们去沙盒目录看一下是不是真的有文件写入。
打开访达
-> cmd + shift + g
->
- 在沙河目录中我们找到了
msgSends-
文件,打开文件看看文件中都有什么。
- 在文件中,我们看到
resolveInstanceMethod:
后边又调用了很多函数,紧接着调用的是forwardingTargetForSelector:
函数和methodSignatureForSelector:
函数,我们接下来对两个函数分别进行探索。
二、消息快速转发流程
我们先来对forwardingTargetForSelector:
函数进行探索,cmd + shift + 0
打开documentation
,然后进行搜索,可以得到下面的结果。
- 通过阅读文档,我们可以知道
forwardingTargetForSelector:
函数有重定向
的作用,可以指定一个对象去完成没有实现的方法
,我们到项目中去实践一下。
新创建一个SSLDirector
类,并在SSLPerson
中添加forwardingTargetForSelector
方法实现。
@interface SSLPerson : NSObject
- (void)say1;
@end
@implementation SSLPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [SSLDirector alloc];
}
@end
@interface SSLDirector : NSObject
- (void)say1;
@end
@implementation SSLDirector
- (void)say1
{
NSLog(@"SSLDirector say1");
}
@end
复制代码
运行程序查看结果:
SSLDirector say1
成功打印,程序没有报错,这种解决方式还是很快速的,但是如果这种方式还是没有解决呢,下面继续探索。
三、消息慢速转发流程
methodSignatureForSelector
cmd + shift + 0
打开documentation
,搜索methodSignatureForSelector:
,可以得到下面的结果。
- 通过阅读文档,我们了解到方法是通过返回方法签名
NSMethodSignature
,以及和forwardInvocation:
方法联合使用,接下来用代码去实现。
在SSLPerson
中添加methodSignatureForSelector
和forwardInvocation:
方法实现。
@interface SSLPerson : NSObject
- (void)say1;
@end
@implementation SSLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(say1)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
}
@end
复制代码
运行程序查看结果:
- 程序没有崩溃,也没有调用方法。
OC
中调用方法是消息
的发送,也可以说是事务
,事务
可做可不做,不做的话也不会报错,接下来我们看一下forwardInvocation
中可以做些什么。
forwardInvocation
cmd + shift + 0
打开documentation
,搜索forwardInvocation:
,可以得到下面的结果。
- 阅读文档,我们下面按照文档再去项目中实现。
我们在forwardInvocation:
方法中添加下面的代码,运行程序:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL aSelector = [anInvocation selector];
SSLDirector *director = [SSLDirector alloc];
if ([director respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:director];
}
}
复制代码
SSLDirector say1
正常打印,程序没有报错。
四、hoper 反汇编 CF
我们重新回到消息转发流程之前,不使用instrumentObjcMessageSends
函数,换一种方式进行分析。
先运行程序:
- 因为我们什么都没有做,所以程序还是会报
unrecognized selector
的错误。 - 通过堆栈的打印,我们发现调用了
___forwarding___
、_CF_forwarding_prep_0
,它们都属于CoreFoundation
,我们去它的源码中找一下。
CF 源码探索
点击 CF 源码 进行下载,搜索forwarding
、prep_0
:
- 如上图,结果是什么也没有搜到,这说明源码并没有开放这部分内容,接下来用反汇编的方式继续探索。
hoper 反汇编 CF
先安装好Hoper
,准备好可执行CoreFoundation
动态库:
将CoreFoundation
动态库拖到Hoper
中打开,全局搜索forwarding
:
- 如上,可以看到
___forwarding_prep_0___
和____forwarding___
相关代码。
1. 反汇编 forwardingTargetForSelector:
点击____forwarding___
:
- 如上图定位代码,判断类是否响应了
forwardingTargetForSelector:
,如果响应了就回去执行,如果没响应就会跳转到loc_64a67
。
2. 反汇编 forwardInvocation:
我们来看下loc_64a67
的相关代码:
- 从
loc_64a67
开始,会先判断对象是不是_NSZombie_(野指针)
,如果是野指针
跳转到loc_64dc1
。 - 如果不是
野指针
,判断类是否相应了methodSignatureForSelector:
,如果没有响应跳转到loc_64dd7
,如果响应了执行这个函数,并继续向下执行。 - 碰到了
_forwardStackInvocation:
,这个是系统内部的方法,没有对外暴露,继续向下执行。 - 到了
loc_64c19
,判断类是否相应了forwardInvocation:
,如果没有响应跳转到loc_64ec2
,如果响应了执行这个函数。
我们通过反汇编的方式,证明了消息转发在底层,是有这很明确的流程的,是有据可依的,另外除了Hoper
,IDA
也可以进行反汇编的探索。