美文网首页
Runtime — 消息转发

Runtime — 消息转发

作者: Dezi | 来源:发表于2020-06-17 18:40 被阅读0次

前言

如果在动态解析阶段不做任何处理的话,我们调用一个未实现的方法会crash,下面来分析一下,crash之前系统还做了什么?

一、探索消息转发

1. instrumentObjcMessageSends打开log开关

#import <Foundation/Foundation.h>
#import "DZStudent.h"

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        DZStudent *student = [DZStudent alloc] ;
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);

    }
    return 0;
}

extern void instrumentObjcMessageSends(BOOL flag):是苹果的私有API,我们可以控制log开关,打印日志信息。

日志文件位置:/tmp/msgSend-xxx

日志文件位置

2. 查看日志文件

我们可以发现,方法在调用报失败doesNotRecognizeSelector之前的调用顺序:resolveInstanceMethod -> forwardingTargetForSelector -> methodSignatureForSelector -> doesNotRecognizeSelector

resolveInstanceMethod是动态方法决议,我们上一文已经做了分析,本文只针对后边的方法进行源码分析。

二、快速转发

1. forwardingTargetForSelector分析

我们全局搜索后,发现这个方法是NSObject中实现的方法,只做了返回nil的操作:

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

我们可以结合方法介绍或者官方文档来进行分析:

2. 方法说明(看discussion):

  1. 该方法的目的就是不能处理方法的时候,交给另外一个对象来执行,但是不能返回self,否则会一直找不到陷入死循环。
  2. 该方法效率很高,如果不实现或者返回nil,会走到相对效率低的forwardInvocation: 方法进行处理。
  3. 所以我们称forwardingTargetForSelector快速转发forwardInvocation慢速转发
  4. 被转发的消息接收者,参数和返回值等需要和原方法相同。

3. 方法使用

当访问DZStudent未实现的saySomething方法时,可以使用- (id)forwardingTargetForSelector:(SEL)aSelector进行方法转发,用DZTeacher这个实现saySomething方法的对象来接收,具体实现代码如下:

main.m

DZStudent *student = [DZStudent alloc];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);

DZStudent.m

// 消息转发流程
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == @selector(saySomething)) {
        return [DZTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

DZTeacher.m

@implementation DZTeacher
- (void)saySomething{
    NSLog(@"%s",__func__);
}
@end

打印结果

// 失败打印
2020-06-17 15:58:28.646428+0800 008-方法查找-消息转发[17172:5671635] -[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980
2020-06-17 15:58:28.658327+0800 008-方法查找-消息转发[17172:5671635] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980'

// 成功打印
2020-06-17 14:58:14.506074+0800 008-方法查找-消息转发[10077:5556271] -[DZStudent forwardingTargetForSelector:] -- saySomething
2020-06-17 14:58:14.507704+0800 008-方法查找-消息转发[10077:5556271] -[DZTeacher saySomething]

通俗点讲,这个方法的作用就是,自己的活自己干不了,就交给能干活的人去干。

三、慢速转发

当快速转发流程也没有实现,或者返回nil,就进入慢速转发流程。

1. methodSignatureForSelector

同样在源码中全局搜索之后,我们发现这个方法也是NSObject中实现的方法:

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

官方文档:

方法说明:

该方法用于协议的实现,如果有对象未能直接实现的消息,则重写此方法返回适当的方法签名。然后将签名对象作为参数传给forwardInvocation方法,在forwardInvocation里边将消息给能处理该消息的对象,避免最后调用didNotRecognizeSelector方法导致崩溃。

下来我们继续了解 forwardInvocation 方法:

2. forwardInvocation

同样是在源码中全局搜索之后,我们发现这个方法也是NSObject中实现的:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

官方文档:

forwardInvocationmethodSignatureForSelector必须是同时重写。并且该方法可以自由指派多个对象接受该消息。

3. doesNotRecognizeSelector

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

我们可以发现,最终是doesNotRecognizeSelector方法抛出的异常,所以我们可以重写forwardInvocation方法,这样不执行父类的方法,程序就不会崩溃了。

4. 方法使用

forwardInvocation方法中,我们可以把这个方法看成是一个未知方法收集箱,在这里可以随意选择你可以处理的方法,进行归类集中处理。

main.m

#import <Foundation/Foundation.h>
#import "DZStudent.h"

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZStudent *student = [DZStudent alloc];
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);
    }
    return 0;
}

DZTeacher.m

@implementation DZTeacher

- (void)saySomething{
    NSLog(@"%s",__func__);
}
@end

DZStudent.m
备注:关于方法签名串"v@:"可以参考官方文档:方法签名Type Encodings

// 返回一个方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);

   SEL aSelector = [anInvocation selector];
   if ([[DZTeacher alloc] respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:[DZTeacher alloc]];
   else
       [super forwardInvocation:anInvocation];
}
// 打印
2020-06-17 17:17:08.148187+0800 008-方法查找-消息转发[24033:5801203] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 17:17:08.149424+0800 008-方法查找-消息转发[24033:5801203] -[DZStudent forwardInvocation:]

当然此处转发方法也可以什么都不做处理,也仅仅是转发不出去而已,并不会崩溃。

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);
}
// 打印
2020-06-17 18:24:21.692113+0800 008-方法查找-消息转发[32243:5925309] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 18:24:21.699464+0800 008-方法查找-消息转发[32243:5925309] -[DZStudent forwardInvocation:]

四、总结

  1. 动态方法决议也没有做处理时,就会进入快速转发(forwardingTargetForSelector)阶段。
  2. 如果快速转发也没有做处理,会继续到慢速转发(forwardInvocation)阶段。
  3. 即使forwardInvocation中不实现后续方法也不会崩溃。
  4. forwardInvocationforwardingTargetForSelector类似,都可以将A类的方法转发的B类的实现中去,但是forwardInvocation优点是更加灵活,forwardingTargetForSelector只能转发发到固定的一个对象。而forwardInvocation可以转发的多个对象中去,甚至不做处理,也仅仅是转发不出去而已,并不会崩溃。

消息的流转及对应的作用如下所示:

流转:消息发送 -> 消息查找 -> 动态方法决议 -> 快速转发 -> 慢速转发
作用:像某个对象或类发送消息 -> 自己有没有处理 -> 自己有没有特殊处理(动态方法决议) -> 指定的人有没有处理 -> 爱谁处理谁处理

相关文章

  • Effective Objective-C读后笔记(2)

    11、runtime消息转发机制 runtime的消息转发流程图消息转发 消息转发的示例实现 这里也给大家推荐一篇...

  • iOS - Runtime - 概念和方法交换

    runtime的概述runtime的相关概念runtime消息机制消息传递动态方法解析消息转发runtime的作用...

  • runtime底层实现原理

    一、Runtime介绍二、Runtime源码初探三、Runtime消息传递四、Runtime消息转发五、Runti...

  • runtime 消息转发

    实例方法的消息传递:消息的转发建立在objc_msgSend(id, SEL, ...)来实现的。首先会在类对象的...

  • Runtime 消息转发

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

  • Runtime消息转发

    我们还是先从实际代码入手吧,首先,我们先新建一个类,就Person类吧,大家刚学OC的时候用的最多的就是Perso...

  • runtime - 消息转发

    通过前边的学习我们知道,某个类或者对象调某个方法实际上就是给这个类/对象发送消息,如果我们某个对象要调用某个方法,...

  • Runtime - 消息转发

    引言 Objective-C作为iOS开发的主力语言(目前),将来必然被Swift替代。我们都知道在OC中我们是使...

  • Runtime消息转发

    消息转发是在查找IMP失败后执行一系列转发流程。 ①动态方法解析: 对象在收到无法解读的消息之后,首先将调用所属类...

  • Runtime — 消息转发

    前言 如果在动态解析阶段不做任何处理的话,我们调用一个未实现的方法会crash,下面来分析一下,crash之前系统...

网友评论

      本文标题:Runtime — 消息转发

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