动态方法解析:
对象在收到无法解读的消息后,调用类方法+ (BOOL)resolveInstanceMethod:(SEL)sel来动态为其新增实例方法以处理该选择子。(如果尚未实现的方法是类方法,则调用+ (BOOL)resolveClassMethod:(SEL)sel)
新建HTResolveMethod类
①HTResolveMethod.h
#import
@interface HTResolveMethod : NSObject
@property (nonatomic, copy) NSString *name;
@end
②HTResolveMethod.m
#import "HTResolveMethod.h"
#import
@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
@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
@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
/** 声明协议,当HTResolveMethod或其子类自定义方法未实现时,保证程序不崩溃 ,弹出提示框,并在控制台输出未实现的方法*/
@protocol ResolveMethodCrashDelegate
- (void)resolveMethodCrashWithSelName:(NSString *)selName;
@end
@interface HTResolveMethod : NSObject
@property (nonatomic, weak) id 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地址