iOS消息机制--动态方法解析、消息转发机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaozhuanddapang/article/details/62336072

动态方法解析:
对象在收到无法解读的消息后,调用类方法+ (BOOL)resolveInstanceMethod:(SEL)sel来动态为其新增实例方法以处理该选择子。(如果尚未实现的方法是类方法,则调用+ (BOOL)resolveClassMethod:(SEL)sel)

新建HTResolveMethod类
①HTResolveMethod.h

#import <Foundation/Foundation.h>

@interface HTResolveMethod : NSObject

@property (nonatomic, copy) NSString *name;

@end

②HTResolveMethod.m

#import "HTResolveMethod.h"
#import <objc/runtime.h>

@implementation HTResolveMethod
@dynamic name;

/**
 *  第一步:动态方法解析,征询接收者,看其是否能动态添加方法,来处理当前这个未知的选择子
 *  为name动态添加set和get方法
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorStr = NSStringFromSelector(sel);
    /**
     *  i(类型为int)
     *  v(类型为void)
     *  @(类型为id)
     *  :(类型为SEL)
     */
    if ([selectorStr isEqualToString:@"setName:"]) {
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
    } else if ([selectorStr isEqualToString:@"name"]) {
        class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
    }
    return [super resolveInstanceMethod:sel];
}

void autoDictionarySetter(id self,SEL _cmd,id value) {
    NSLog(@"name的set方法==%@",value);
}

id autoDictionaryGetter(id self,SEL _cmd) {
    return @"name的get方法";
}

@end

③在ViewController.m中调用

HTResolveMethod *resolveMethod = [[HTResolveMethod alloc] init];
resolveMethod.crashDelegate = self;
resolveMethod.name = @"颖宝";
NSLog(@"%@",resolveMethod.name);

重定向(备援接收者)
当不能在+ (BOOL)resolveInstanceMethod:(SEL)sel中动态添加方法处理选择子时,当前接收者还有一次机会处理未知的选择子。可以在- (id)forwardingTargetForSelector:(SEL)aSelector中把这条消息转给其他接收者来处理。
新建HTResolveMethod类
①HTResolveMethod.h

#import <Foundation/Foundation.h>

@interface HTResolveMethod : NSObject

- (void)setupDatasWithTitle:(NSString *)title;

@end

②HTResolveMethod.m

#import "HTResolveMethod.h"
#import "HTForwardingTarget.h"

@implementation HTResolveMethod

/**
 *  第二歩:进入消息转发流程重定向
 *  将setupDatasWithTitle:转发到HTForwardingTarget类
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorStr = NSStringFromSelector(aSelector);
    if ([selectorStr isEqualToString:@"setupDatasWithTitle:"]) {
        HTForwardingTarget *forwardingTarget = [HTForwardingTarget new];
        return forwardingTarget;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

新建HTForwardingTarget类
③HTForwardingTarget.m

- (void)setupDatasWithTitle:(NSString *)title {
    NSLog(@"重定向成功了,%@",title);
}

④在ViewController.m中调用以下方法

HTResolveMethod *resolveMethod = [[HTResolveMethod alloc] init];
[resolveMethod setupDatasWithTitle:@"我的日记"];

完整的消息转发
如果备援接收能未能处理选择子,会调用- (NSMethodSignature )methodSignatureForSelector:(SEL)aSelector生成方法签名,然后系统用这个方法签名生成NSInvocation对象。NSInvocation对象包含选择子、目标及参数。之后调用- (void)forwardInvocation:(NSInvocation )anInvocation方法改变调用目标,使消息在新目标上得以调用。这种方法有两种实现方式:一种实现方式与调用备援接收者方法有异曲同工的作用,而越往后面处理消息的代价就越大,所以不推荐在此方法中实现类似效果。另一种实现方式是改变消息内容或是改变选择子。

新建HTResolveMethod类
①HTResolveMethod.h

#import <Foundation/Foundation.h>

@interface HTResolveMethod : NSObject

- (void)setupDatasWithTitle:(NSString *)title;

@end

②HTResolveMethod.m

扫描二维码关注公众号,回复: 2918372 查看本文章
#import "HTResolveMethod.h"
#import "HTForwardingTarget.h"

@implementation HTResolveMethod

/**
 *  第二歩:进入消息转发流程重定向
 *  将setupDatasWithTitle:转发到HTForwardingTarget类
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorStr = NSStringFromSelector(aSelector);
    if ([selectorStr isEqualToString:@"setupDatasWithTitle:"]) {
        HTForwardingTarget *forwardingTarget = [HTForwardingTarget new];
        return forwardingTarget;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

新建HTForwardingTarget类
③HTForwardingTarget.m

//第三步,生成方法签名,然后系统用这个方法签名生成NSInvocation对象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *selectedStr = NSStringFromSelector(aSelector);
    if ([selectedStr isEqualToString:@"setupDatasWithTitle:"]) {
        NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        return sign;
    }
    return [super methodSignatureForSelector:aSelector];
}

//第四步,改变选择子
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    HTForwardInvocation *forwardInvocation = [[HTForwardInvocation alloc] init];
    anInvocation.selector =  NSSelectorFromString(@"setMsg:");
    if ([forwardInvocation respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:forwardInvocation];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

新建HTForwardInvocation类
④HTForwardInvocation.m

- (void)setMsg:(NSString *)msg {
    NSLog(@"选择子被改变了,%@",msg);
}

⑤在ViewController.m中调用以下内容

HTResolveMethod *resolveMethod = [[HTResolveMethod alloc] init];
[resolveMethod setupDatasWithTitle:@"我的日记"];

如果最终方法仍未实现,则调用NSObject的- (void)doesNotRecognizeSelector:(SEL)aSelector方法抛出异常

那么问题来了,知道动态方法解析、消息转发机制有什么用呢?我们举个简单的小例子:
新建HTResolveMethod类
①HTResolveMethod.h

#import <Foundation/Foundation.h>

/** 声明协议,当HTResolveMethod或其子类自定义方法未实现时,保证程序不崩溃 ,弹出提示框,并在控制台输出未实现的方法*/
@protocol ResolveMethodCrashDelegate <NSObject>

- (void)resolveMethodCrashWithSelName:(NSString *)selName;

@end

@interface HTResolveMethod : NSObject

@property (nonatomic, weak) id<ResolveMethodCrashDelegate> crashDelegate;

@end

②HTResolveMethod.m

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    return sign;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    [super forwardInvocation:anInvocation];
}

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSString *selectedStr = NSStringFromSelector(aSelector);
    [self crashHandle:selectedStr];
}

- (void)crashHandle:(NSString *)selName {
    if (self.crashDelegate && [self.crashDelegate respondsToSelector:@selector(resolveMethodCrashWithSelName:)]) {
        [self.crashDelegate resolveMethodCrashWithSelName:selName];
    }
}

新建HTResolveSonMethod类(只声明不实现)
③HTResolveSonMethod.h

#import "HTResolveMethod.h"

@interface HTResolveSonMethod : HTResolveMethod

- (void)tapNextButtonWithTag:(NSInteger)tag;

@end

④ViewController.m中调用以下方法

#import "ViewController.h"
#import "HTResolveSonMethod.h"

@interface ViewController ()<
ResolveMethodCrashDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor purpleColor];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(30, 100, CGRectGetWidth(self.view.bounds)-60, 30);
    [button setTitle:@"准备崩溃" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonClick:(id)sender {
    HTResolveSonMethod *resolveSonMethod = [[HTResolveSonMethod alloc] init];
    resolveSonMethod.crashDelegate = self;
    [resolveSonMethod tapNextButtonWithTag:4];
}

#pragma ResolveMethodCrashDelegate
- (void)resolveMethodCrashWithSelName:(NSString *)selName {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示信息" message:[NSString stringWithFormat:@"崩溃方法:%@",selName] preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
    [alertController addAction:cancelAction];
    [self presentViewController:alertController animated:YES completion:nil];
    NSLog(@"此方法不存在selName==%@",selName);
}

@end

效果如图:
效果图

demo地址

猜你喜欢

转载自blog.csdn.net/xiaozhuanddapang/article/details/62336072