美文网首页
消息转发

消息转发

作者: Wayne_Wang | 来源:发表于2021-07-20 19:01 被阅读0次
1.jpeg

承接上一篇文章消息动态决议最后我们留下的疑问和初步猜测我们进一步去分析和探索。就是在动态方法决议之后还是没有找到对应的imp,在lookUpImpOrForward方法里又没有明显的下一步承接流程的入口。那么系统是怎么处理和运行下一步流程机制的呢?我们前面利用查看系统日志的方法找到了几个方法流程。即:

resolveInstanceMethod
forwardingTargetForSelector
methodSignatureForSelector
doesNotRecognizeSelector

下面我们来验证和探索下。

ps:探索步骤我放在最后的的探索步骤里。这里我们先直接就把探索分析的结果拿出来:

快速转发

forwardingTargetForSelector

我们不知道这个方法是干嘛用的,那我们就去查看下文档,commad+shift+0打开文档 粘贴搜索这个方法查看解释:

2.png

大概意思就是:''当一个对象实现的方法找不到返回了一个非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.

3.png

接下来我们在这个方法了做一些处理,做到消息转发的实现:
我们创建一个ZYIoser类实现上面的这个zyEatSugar方法,在personforwardingTargetForSelector方法里实现转发到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:

5.png

在这里我们发现他主要是用于协议的实现,还必须配备了-forwardInvocation:方法一起使用。如果我们要进行处理就要对这个方法返回一个适当的方法签名。

接下来我们来看看这个所谓的签名是个什么东西,直接进入methodSignatureForSelector:方法的的返回值查看:

6.png

这样我们再去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:方法的文档介绍:

8.png 9.png 10.png

从上面我们再次看到要求我们这两个方法配搭使用,并且给一个符合苹果要求格式的签名。并且最后告诉我们可以在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,我们在之前看到的报错信息并没有什么特别有用的信息,那我么下面利用lldbbt命令来查看下找不到方法报错时候的堆栈信息:

11.png

从上面我们可以发现有我们感兴趣的方法doesNotRecognizeSelector并且在这之前调用了CoreFoundation___forwarding____CF_forwarding_prep_0

2,我们去查找CoreFoundation的时候发现它并没有开源,在苹果的文档也没找到什么特别有用的信息。所以我们尝试找一个CoreFoundation动态库利用反汇编的方式来查看下。
我下面利用Hopper工具。直接把动态库CoreFoundation拉倒Hopper打开并且搜索forwarding发现可以找到:

12.png

点击上图中的____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

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!

相关文章

  • Runtime

    相关简单介绍 消息机制消息传递机制消息转发机制-动态添加方法消息转发机制-快速转发消息转发机制-慢速转发消息转发机...

  • 消息转发机制(动态消息转发)

    例子分析 1)在给程序添加消息转发功能以前,必须覆盖两个方法,即methodSignatureForSelecto...

  • Runtime 消息转发

    目录 消息转发背景知识 消息转发使用方式 消息转发常见问题 消息转发背景知识 1.消息转发的定义Objective...

  • 消息转发

    参考:https://www.jianshu.com/p/76ed71216cde

  • 消息转发

    执行一个没有实现的方法,程序会在运行时挂掉并抛出 unrecognized selector sent to … ...

  • 消息转发

    OC中的方法调用,其实都是转化成objc_msgSend函数调用 1.信息发送 2.动态方法解析 /// 对象消息解析

  • 消息转发

    1. 消息查找 Objective-C 具有很强的动态性,它将静态语言在编译和链接时期做的工作,放置到运行时来处理...

  • 消息转发

    一张图说明消息转发 例如我们进行这样一个操作:People这个类并没有实现perfectGotoSchool这个函...

  • 消息转发

  • 消息转发

    title: 消息转发date: 2017-07-06 15:32:45tags: Method resoluti...

网友评论

      本文标题:消息转发

      本文链接:https://www.haomeiwen.com/subject/ejolmltx.html