在上篇objc_msgSend慢速查找中我们知道了通过慢速查找依然找不到方法实现的情况下程序就会崩溃!再此之前苹果也会给机会防止程序崩溃,
动态方法决议
和消息转发
.
如果没有实现上述过程怎么样?
新建类PHFather
中一个实例方法和类方法,且在.m
中没有实现
运行结果如下图:
这个后边分析!
根据慢速查找发现方法找不到最后都走到__objc_msgForward_impcache
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
...
for (unsigned attempts = unreasonableClassCount();;) {
...
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
...
...
}
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
- 首先赋值
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
- 接下来进行查找如果没有找到方法到最终实现 赋值
imp = forward_imp;
- 查找
objc_msgForward_impcache
看内部是怎么实现到
全局搜索objc_msgForward_impcache
可以看到是汇编代码实现,真机arm64
架构下查找
- 汇编实现中查找
__objc_forward_handler
,并没有找到,在源码中去掉一个下划线进行全局搜索_objc_forward_handler
,有如下实现,本质是调用的objc_defaultForwardHandler
方法
多么熟悉到一句话!!!
- 在慢速查找的流程中,我们了解到,如果
快速+慢速
没有找到方法实现,动态方法决议
也没有找到会怎么处理? - 在前面的程序崩溃例子中可以看到找不到方法崩溃控制太打印了
unrecognized selector sent ...
的提示,同时也打印了堆栈信息: - 这里有两种查找崩溃底层调用信息的方法
就使用消息转发,但是,我们找遍了源码也没有发现消息转发的相关源码,可以通过以下方式来了解,方法调用崩溃前都走了哪些方法
- 通过
instrumentObjcMessageSends
方式打印发送消息的日志 - 通过
hopper/IDA
反编译
1.instrumentObjcMessageSends
方式
-lookUpImpOrForward->log_and_fill_cache->logMessageSend
中找到决定打印的bool值objcMsgLogEnabled
其实现在此方法后instrumentObjcMessageSends
- 因为此函数存在
.mm中
且是C语言函数
,调用的时候需要扩展- 1.在
main文件
中通过extern
声明instrumentObjcMessageSends
方法 - 2.打开
objcMsgLogEnabled
开关调用instrumentObjcMessageSends
方法时,传入YES
通过logMessageSend
源码,了解到消息发送打印信息存储在/tmp/msgSends
目录,如下所示
运行代码并前往文件夹/tmp/msgSends
发现带有msgSends
前缀的文件双击打开如图:
- 1.在
注意:如果打开文件里面没有打印信息
可见找不到方法执行的时候内部方法的实现情况是:
-
两次动态方法决议:
resolveInstanceMethod
方法 -
两次消息快速转发:
forwardingTargetForSelector
方法 -
两次消息慢速转发:
methodSignatureForSelector + resolveInstanceMethod
2.hopper/IDA
反编译
Hopper和IDA是一个可以帮助我们静态分析可视性文件的工具,可以将可执行文件反汇编成伪代码、控制流程图等,因为hopper这个正式版付费,下载链接: https://pan.baidu.com/s/1wSDIsM-OJn_4tvXIfV6_UA 密码: 5vim
-
通过查看刚开始的崩溃信息可知
___forwarding___
来自CoreFoundation
bt打印堆栈
信息如下:
image list
打印可执行文件路径找到CoreFoundation
的路径/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
前往CoreFoundation文件夹找到可执行文件拖入hopper
搜索__forwarding_prep_0___
然后伪代码显示
双击_forwarding
,伪代码实现如下 伪代码-forwardingTargetForSelector
如果没有响应,跳转至loc_64a67即快速转发没有响应,进入慢速转发流程
-
如果没有响应
methodSignatureForSelector:
方法跳转loc_64dd7报错 -
如果方法签名方法为空跳转loc_64e3c 则直接报错
-
如果
methodSignatureForSelector
返回值不为空,则在forwardInvocation
方法中对invocation
进行处理
通过以上两种方式分析消息转发方式有两个阶段 -
快速转发
forwardingTargetForSelector
-
慢速转发
methodSignatureForSelector
forwardInvocation
消息转发的处理主要分为两部分:
- 动态方法决议
- 消息转发
1.快速转发
- 当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到
forwardingTargetForSelector
方法 - 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程
如果返回nil,则进入慢速消息转发
2.慢速转发
- 执行到
methodSignatureForSelector
方法 - 如果返回的方法签名为nil,则直接崩溃报错
- 如果返回的方法签名不为nil,走到
forwardInvocation
方法中,对invocation事务
进行处理,如果不处理也不会报错
针对于动态方法决议和消息转发原理做以下解决方案
1.动态方法决议
在PHFather类的.m中添加resolveInstanceMethod
方法并把方法sdd
实现指向dynamicMethodIMP
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@" >> Instance resolving %@", NSStringFromSelector(sel));
if (sel == @selector(sdd)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
打印如下图
2.快速转发
在main.m
方法中添加类PHAnimal
声明并实现sdd方法
@interface PHAnimal : NSObject
-(void)sdd;
@end
@implementation PHAnimal
-(void)sdd{
NSLog(@"%s",__func__);
}
@end
在PHFather
中添加实现方法:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
//将消息的接收者指定为PHPerson,在PHPerson中查找sdd的实现
return [PHPerson alloc];
}
运行结果
如果不指定消息接受者,直接调用父类方法如果没有找到还直接报错:
3.慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
}
打印结果如下,forwardInvocation
方法中不对invocation
进行处理,也不会崩溃报错
对
forwardInvocation
方法中的invocation
进行处理,如图所以,由上述可知,无论在
forwardInvocation
方法中是否处理invocation
事务,程序都不会崩溃。
-
那么动态方法决议和转发的数据如下图
从此处可以看出动态方法决议走了两次:
- 第一次:正常的动态方法决议
resolveInstanceMethod
- 第二次:在第一次动态方法决议还是没有找到走了
快速转发forwardingTargetForSelector
和慢速转发methodSignatureForSelector
,在方法签名之后和处理forwardInvocation事件
之前又进行了动态方法决议,那么可以确定的一点儿是第二次动态方法决议的调用肯定是快速转发forwardingTargetForSelector
为什么?看下篇分析
网友评论