承接上一篇文章消息动态决议最后我们留下的疑问和初步猜测我们进一步去分析和探索。就是在动态方法决议之后还是没有找到对应的imp
,在lookUpImpOrForward
方法里又没有明显的下一步承接流程的入口。那么系统是怎么处理和运行下一步流程机制的呢?我们前面利用查看系统日志的方法找到了几个方法流程。即:
resolveInstanceMethod
forwardingTargetForSelector
methodSignatureForSelector
doesNotRecognizeSelector
下面我们来验证和探索下。
ps:探索步骤我放在最后的的探索步骤里。这里我们先直接就把探索分析的结果拿出来:
快速转发
forwardingTargetForSelector
我们不知道这个方法是干嘛用的,那我们就去查看下文档,commad+shift+0
打开文档 粘贴搜索这个方法查看解释:
大概意思就是:''当一个对象实现的方法找不到返回了一个非nil的结果,那么将会把这个消息转发给这个新的对象。如果我们当您只是想将消息重定向到另一个对象这个方法对我们会很有帮助,如果我们只是想要捕获NSInvocation或在转发期间操作参数或返回值那这个方法对我们来说就无所谓了。"
直白点说就是这个方法是一个快速转发流程的方法。如果我们想要对这个消息有更多的操作和处理那么就去找 forwardInvocation:
这个方法。
接下来我们来尝试下。
代码如下:
ZYPerson.h:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson : NSObject
- (void)zyEatSugar;
@end
NS_ASSUME_NONNULL_END
ZYPerson.m:
#import "ZYPerson.h"
@implementation ZYPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
@end
main.m:
#import <Foundation/Foundation.h>
#import "ZYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *person = [ZYPerson alloc];
//调用ZYPerson 的zyEatSugar 方法 ,该方法未实现
[person zyEatSugar];
}
return 0;
}
运行代码就会崩溃但是我们看打印有走到我们的消息转发方法forwardingTargetForSelector
.
接下来我们在这个方法了做一些处理,做到消息转发的实现:
我们创建一个ZYIoser
类实现上面的这个zyEatSugar
方法,在person
的forwardingTargetForSelector
方法里实现转发到ZYIoser
类。代码如下:
ZYPerson.h:
#import "ZYPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZYIoser : ZYPerson
- (void)zyEatSugar;
@end
NS_ASSUME_NONNULL_END
ZYPerson.m:
#import "ZYIoser.h"
#import <objc/message.h>
@implementation ZYIoser
- (void)zyEatSugar
{
NSLog(@"%@ - %s",self,__func__);
}
@end
ZYPerson.h:
#import "ZYPerson.h"
#import "ZYIoser.h"
@implementation ZYPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [ZYIoser alloc];
}
@end
运行代码结果如下:
4.png这样就能防止方法找不到而程序崩溃。但是如果我们的ZYIoser
也没实现的时候那就还是会报错。这个时候就会走到我们日志里看到的methodSignatureForSelector
方法。即慢速转发流程。
慢速转发
同样的方法去查看文档:
methodSignatureForSelector:
在这里我们发现他主要是用于协议的实现,还必须配备了-forwardInvocation:
方法一起使用。如果我们要进行处理就要对这个方法返回一个适当的方法签名。
接下来我们来看看这个所谓的签名是个什么东西,直接进入methodSignatureForSelector:
方法的的返回值查看:
这样我们再去ZYPerson.m
里加入这两个方法并且实现以下:
ZYPerson.h
#import "ZYPerson.h"
#import "ZYIoser.h"
@implementation ZYPerson
//快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(zyEatSugar)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation");
}
@end
运行结果:
7.png不再报错而且走了我们的方法。而且这个invocation
关于我们这个方法的信息的内容还是存在的。但是我们发现我们的方法调用结果却是没有了。这就给我们更多遐想的空间了。我们如果需要使用到这个保存的invocation
信息我们照样可以用,如果不想用我们就不处理就好。
下面我们再看看forwardInvocation:
方法的文档介绍:
从上面我们再次看到要求我们这两个方法配搭使用,并且给一个符合苹果要求格式的签名。并且最后告诉我们可以在forwardInvocation:
方法里对这个方法做指定转发。
示例:
//慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(zyEatSugar)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation: %@ - %@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
ZYIoser *ioser = [ZYIoser alloc];
if ([self respondsToSelector:anInvocation.selector]) {//如果是本类的方法就本类处理
[anInvocation invoke];
}else if ([ioser respondsToSelector:anInvocation.selector]){//是ZYIoser的方法就去ZYIoser里去处理
[anInvocation invokeWithTarget:ioser];
}else{//不处理 可以在这里上报给后台
NSLog(@"%s - %@",__func__,NSStringFromSelector(anInvocation.selector));
}
}
探索步骤:
1,我们在之前看到的报错信息并没有什么特别有用的信息,那我么下面利用lldb
的bt
命令来查看下找不到方法报错时候的堆栈信息:
从上面我们可以发现有我们感兴趣的方法doesNotRecognizeSelector
并且在这之前调用了CoreFoundation
的___forwarding___
和_CF_forwarding_prep_0
。
2,我们去查找CoreFoundation
的时候发现它并没有开源,在苹果的文档也没找到什么特别有用的信息。所以我们尝试找一个CoreFoundation
动态库利用反汇编的方式来查看下。
我下面利用Hopper
工具。直接把动态库CoreFoundation
拉倒Hopper
打开并且搜索forwarding
发现可以找到:
点击上图中的____forwarding___
跳转到这个方法的伪代码:
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
rdi = arg0;
r15 = rdi;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = *_objc_msgSend_stret;
}
else {
r12 = *_objc_msgSend;
}
rax = rcx;
rbx = *(r15 + rax * 0x8);
rcx = *(r15 + rax * 0x8 + 0x8);
var_140 = rcx;
r13 = rax * 0x8;
if ((rbx & 0x1) == 0x0) goto loc_649bb;
loc_6498b:
rcx = **_objc_debug_taggedpointer_obfuscator;
rcx = rcx ^ rbx;
rax = rcx >> 0x1 & 0x7;
if (rax == 0x7) {
rcx = rcx >> 0x4;
rax = (rcx & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_64d48;
loc_649bb:
var_148 = r13;
var_138 = r12;
var_158 = rsi;
rax = object_getClass(rbx);
r12 = rax;
r13 = class_getName(rax);
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;
loc_649fc:
rdi = rbx;
rax = [rdi forwardingTargetForSelector:var_140];
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
loc_64a19:
r12 = var_138;
r13 = var_148;
if ((rax & 0x1) == 0x0) goto loc_64a5b;
loc_64a2b:
rdx = **_objc_debug_taggedpointer_obfuscator;
rdx = rdx ^ rax;
rcx = rdx >> 0x1 & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x4 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_64d45;
loc_64a5b:
*(r15 + r13) = rax;
r15 = 0x0;
goto loc_64d82;
loc_64d82:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_64d45:
rbx = rax;
goto loc_64d48;
loc_64d48:
if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
loc_64d55:
*(r15 + r13) = _getAtomTarget(rbx);
___invoking___(r12, r15);
if (*r15 == rax) {
*r15 = rbx;
}
goto loc_64d82;
loc_64ed1:
____forwarding___.cold.4();
rax = *(rdi + 0x8);
return rax;
loc_64a67:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
r14 = var_138;
var_148 = r15;
if (rax == 0x0) goto loc_64dd7;
loc_64ab2:
rax = [r14 methodSignatureForSelector:var_140];
rbx = var_158;
if (rax == 0x0) goto loc_64e3c;
loc_64ad5:
r12 = rax;
rax = [rax _frameDescriptor];
r13 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
rax = sel_getName(var_140);
rcx = "";
if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
r8 = "";
if (rbx == 0x0) {
r8 = " not";
}
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, stack[-360]);
}
rax = object_getClass(r14);
rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
var_150 = r13;
if (rax == 0x0) goto loc_64c19;
loc_64b6c:
if (*0x5c2700 != 0xffffffffffffffff) {
dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
r15 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *0x5c26f8;
rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
r13 = &stack[-360];
__bzero(r13, rsi);
___chkstk_darwin(r13, rsi, r12, rcx);
rax = objc_constructInstance(*0x5c26f0, r13);
var_140 = r15;
[r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
[var_138 _forwardStackInvocation:r13];
r14 = 0x1;
goto loc_64c76;
loc_64c76:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *var_150;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rcx = *(int32_t *)(rax + 0x1c);
rdx = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
}
}
rax = [r12 methodReturnType];
rbx = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (r14 != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
[r13 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm { fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (r14 != 0x0) {
r15 = ____forwarding___.placeholder;
[r13 release];
}
}
goto loc_64d82;
loc_64c19:
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
[r14 forwardInvocation:rax];
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = &var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
loc_64e3c:
rax = sel_getName(var_140);
r14 = rax;
rax = sel_getUid(rax);
if (rax != var_140) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[-360]);
}
if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) {
____forwarding___.cold.2(var_138);
}
(*_objc_msgSend)(var_138, @selector(doesNotRecognizeSelector:));
asm { ud2 };
rax = loc_64ec2(rdi, rsi);
return rax;
loc_64dd7:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[-360]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[-360]);
}
goto loc_64e3c;
loc_64dc1:
r14 = @selector(forwardingTargetForSelector:);
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_64dd7;
}
从上面的伪代码分析我们可以大致得到以下的信息:
(1).当消息发送流转到CoreFoundation
的__forwarding__
方法的时候就会进行判断当前的类是否实现了forwardingTargetForSelector
,如果实现了就走这里,走消息慢速转发。不行就报错
(2).如果没有实现就判断对象是否能响应methodSignatureForSelector
方法。如果能响应就走methodSignatureForSelector
。不行就报错
(3).当拿到签名后再次判断是否能响应forwardStackInvocation
方法(这个方法没有暴露出来是系统内部的,因为我直接在ZYPerson.m
文件尝试调用和实现 发现不存在)。如果可以响应就 继续往下走不行就报错
(4)判断是否能响应forwardInvocation
方法如果可以就走forwardInvocation
不行就再次报错。
所以我们也就得出了一个结果就是从第一步开始就分成了两条线路:
第一条是走
forwardingTargetForSelector
——消息转发流程
第二条是走methodSignatureForSelector
+forwardInvocation
——消息慢速转发流程
也就是我们上面得出的结论。
总结
综合前面的消息动态决议文章和本文——消息转发。我们得到以下的结论:(以流程图展示)
13.jpg遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!
网友评论