美文网首页
第12条:理解消息转发机制

第12条:理解消息转发机制

作者: MrSYLong | 来源:发表于2018-08-14 23:21 被阅读11次

当对象接收到无法解读的消息后,就会启动“消息转发”机制。

消息转发分为两大阶段:
第一阶段:征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择器”。这叫做“动态方法解析”。
第二阶段:涉及“完整的消息转发机制”。运行时系统会请求接受者以其他手段来处理与消息相关的方法调用。分两小步:
第一步:请接收者看看有没有其他对象能处理未知消息,若有,则运行时系统会把消息转给那个对象。这叫做“备援接收者”。若没有进行第二步。
第二步:启动完整的消息转发机制,运行时系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前的未知消息。

动态方法解析

对象在收到无法解读的消息后,调用其所属类的下列方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel;

说明:
参数:未知消息
返回类型:Boolean类型,表示这个类是否能新增一个实例方法来处理未知消息。

类在收到无法解读的消息后,调用下列方法:

+ (BOOL)resolveClassMethod:(SEL)sel;

使用这种办法的前提:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。

备援接收者

运行时系统请求类,能否将未知消息转给其他接收者来处理:

- (id)forwardingTargetForSelector:(SEL)aSelector;

说明:
参数:未知的选择器。
若当前接收者能找到备援对象,则将其返回,若找不到,返回nil。

在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理未知消息的相关内部对象返回,外界看来,就像是该对象亲自处理这条消息。

注意:若是想在发送备援接收者之前先修改消息内容,那只能通过完整的消息转发机制来做。

完整的消息转发

首先,创建NSInvocation对象,把尚未处理的消息的有关全部细节都封于其中。此对象包含选择器、目标及参数。
在触发NSInvocation对象时,“消息派发系统”把消息派给目标对象。

- (void)forwardInvocation:(NSInvocation *)anInvocation;

说明:
只需改变调用目标,使消息在新目标上得以调用即可。

注意:
在触发消息前,先以某种方式来改变消息内容,比如追加另外一个参数,或是改换选择器等,这是比较有用的实现方法。
如果不改变消息内容,只是使未知消息在新目标上得以调用,那与“备援接收者”方案实现方法等效,使用“备援接收者”方法即可。

实现此方法时,若发现某调用操作不应由本类处理,则需要调用超类的同名方法。这样,集成体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类的方法,那么该方法还会继而调用"doesNotRecognizeSelector:"以抛出异常,此异常表明选择器最终未能得到处理。

消息转发全流程

消息转发.png

消息转发,步骤越往后,处理消息的代价就越大,最好在第一步就处理完,这样,运行时系统可以将此方法缓存起来,如果类的实例稍后收到同名选择器,那就无须启动消息转发流程。
如果不修改消息内容,则在第二步进行消息转发即可。

例子

// 头文件
#import <Foundation/Foundation.h>

@interface EOCAutoDictionary : NSObject

@property (nonatomic, strong) NSString *string;

@property (nonatomic, strong) NSNumber *number;

@property (nonatomic, strong) NSDate *date;

@property (nonatomic, strong) id opaqueObject;

@end

// 实现文件
#import "EOCAutoDictionary.h"
#import <objc/runtime.h>
#import "EOCAutoHelper.h"

@interface EOCAutoDictionary()

@property (nonatomic, strong) NSMutableDictionary *backingStore;

@property (nonatomic, strong) EOCAutoHelper *heper;

@end

@implementation EOCAutoDictionary

@dynamic string, number, date, opaqueObject;

- (instancetype)init
{
    self = [super init];
    if (self) {
        _backingStore = [NSMutableDictionary new];
        _heper = [EOCAutoHelper new];
    }
    return self;
}

// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *selectorString = NSStringFromSelector(sel);
    
    if ([selectorString isEqualToString:@"autoHeplerMethod"]) {
        // 备援接受者
        return NO;
    } else {
        // 动态方法解析
        if ([selectorString hasPrefix:@"set"]) {
            // 向类中动态添加方法
            // 参数说明:类,选择器,待添加的函数指针,类型编码(返回值类型@:参数)
            class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
        } else {
            class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }
    return NO;
}

// 备援接受者
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *selectorString = NSStringFromSelector(aSelector);
    if ([selectorString isEqualToString:@"autoHeplerMethod"]) {
        // 返回一个内部对象来代替实现方法
        return _heper;
    }
    return [super forwardingTargetForSelector:aSelector];
}

id autoDictionaryGetter(id self, SEL _cmd)
{
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *key = NSStringFromSelector(_cmd);
    return [backingStore objectForKey:key];
}

void autoDictionarySetter(id self, SEL _cmd, id value)
{
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    
    // 删除':'
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    // 删除'set'
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    // 将第一个字符串变为小写
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];
    }
}

@end

// 头文件
#import <Foundation/Foundation.h>

@interface EOCAutoHelper : NSObject

- (void)autoHeplerMethod;

@end

// 实现文件
#import "EOCAutoHelper.h"

@implementation EOCAutoHelper

- (void)autoHeplerMethod
{
    NSLog(@"EOCAutoHelper");
}

@end

// 使用
EOCAutoDictionary *dict = [EOCAutoDictionary new];

// 测试动态方法解析
dict.date = [NSDate dateWithTimeIntervalSince1970:475372800];
NSLog(@"dict.date : %@",dict.date);

// 测试备援接收者
[dict performSelector:@selector(autoHeplerMethod)];

相关文章

  • 《Effective Objective-C 2.0 》 阅读笔

    第12条:理解消息转发机制 1. 消息转发机制 当对象接收到无法解读的消息后,就会启动“消息转发”机制,开发者可经...

  • 深入理解Object-C消息转发机制

    深入理解Object-C消息转发机制 深入理解Object-C消息转发机制

  • iOS理解Objective-C中消息转发机制附Demo

    iOS理解Objective-C中消息转发机制附Demo iOS理解Objective-C中消息转发机制附Demo

  • 理解消息转发机制

    1.当对象接收到无法解读的消息之后,就会启动消息转发机制,程序员可经由此告诉对象应该如何处理位置消息。 2.消息转...

  • Runtime

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

  • 《Effective Objective-C 2.0》 阅读笔记

    12. 理解消息转发(message forwarding) 紧接着第11条的消息传递机制,如果对象无法解读接收到...

  • 第12条:理解消息转发机制

    当对象接收到无法解读的消息后,就会启动“消息转发”机制。 消息转发分为两大阶段:第一阶段:征询接收者,所属的类,看...

  • runtime系列文章总结

    《iOS Runtime详解(消息机制,类元对象,缓存机制,消息转发)》《消息转发机制与Aspects源码解析》《...

  • iOS消息转发机制

    消息转发机制: 消息转发机制是相对于消息传递机制而言的。 1、消息(传递)机制 RunTime简称运行时。就是系统...

  • 11.理解消息转发机制

    1、当对象接收到无法解读的消息,就会启动消息转发机制。 2、开发者在写自己的类时,可以转发过程中设置挂钩,用以执行...

网友评论

      本文标题:第12条:理解消息转发机制

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