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

来源:互联网 发布:linux下python mmap 编辑:程序博客网 时间:2024/05/19 03:41

动态方法解析:
对象在收到无法解读的消息后,调用类方法+ (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

#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地址

1 0
原创粉丝点击