NSMethodSignature与NSInvocation
NSMethodSignature与NSInvocation简单使用
如果我们只知道一个方法的名称@"test:",那如何调用这个方法呢?
我们的第一反应肯定是会想到通过 performSelector: withObject: 调用:
SEL selector = NSSelectorFromString(@"test:");
[instance performSelector:selector withObject:object];
这一点问题也没有,但如果这个test方法是多参数的或者有返回值的,这时performSelector肯定是行不通的。这时我们就需要用到NSMethodSignature与NSInvocation了,直接贴代码:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self invocationMethod];
}
- (void)invocationMethod {
// method1
SEL selector1 = @selector(method1);
NSMethodSignature *signature1 = [[self class] instanceMethodSignatureForSelector:selector1]; // selector ====> signature
NSInvocation *invocation1 = [NSInvocation invocationWithMethodSignature:signature1]; // signature ====> invocation
invocation1.target = self;
invocation1.selector = selector1;
[invocation1 invoke];
// method2
SEL selector2 = @selector(method2);
NSMethodSignature *signature2 = [[self class] instanceMethodSignatureForSelector:selector2];
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:signature2];
invocation2.target = self;
invocation2.selector = selector2;
[invocation2 invoke];
NSString *value2 = @"";
[invocation2 getReturnValue:&value2];
NSLog(@"%@",value2);
// method3
SEL selector3 = @selector(method3:argument2:);
NSMethodSignature *signature3 = [[self class] instanceMethodSignatureForSelector:selector3];
NSInvocation *invocation3 = [NSInvocation invocationWithMethodSignature:signature3];
invocation3.target = self;
invocation3.selector = selector3;
NSString *argument1 = @"argument1";
NSString *argument2 = @"argument2";
[invocation3 setArgument:&argument1 atIndex:2]; // 方法实际上有两个隐藏参数:self和_cmd 这里参数的index为2
[invocation3 setArgument:&argument2 atIndex:3];
[invocation3 invoke];
}
- (void)method1 {
NSLog(@"method1");
}
- (NSString *)method2 {
NSLog(@"method2");
return @"method2 return";
}
- (void)method3:(NSString*)argument1 argument2:(NSString *)argument2 {
NSLog(@"method3");
NSLog(@"%@,%@",argument1,argument2);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
实现起来会有点繁琐,但却很实用,运行的结果:
2018-02-09 15:53:17.591 [4807:606671] method1
2018-02-09 15:53:17.592 [4807:606671] method2
2018-02-09 15:53:17.592 [4807:606671] method2 return
2018-02-09 15:53:17.592 [4807:606671] method3
2018-02-09 15:53:17.592 [4807:606671] argument1,argument2
NSMethodSignature与NSInvocation应用场景
消息转发
- 将方法交由另一个类实现
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UILabel *label;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
[self.view addSubview:_label];
[self performSelector:@selector(setText:) withObject:@"viewController"];
}
// 重载methodSignatureForSelector:得到一个方法aSelector的签名,再由后面的forwardInvocation:去执行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [_label methodSignatureForSelector:aSelector];
}
return signature;
}
// forwardInvocation:执行从methodSignatureForSelector:返回的NSMethodSignature,并将NSInvocation转发到其他对象
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([_label respondsToSelector:selector]) {
[anInvocation invokeWithTarget:_label];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
(UIViewController包含了UIlabel 属性 label, 如果UIViewController 实例调用setText:方法,由于类没有setText:方法,将会转发给 label 实现)
UILabel文字设置成功
- 向NSNull发送消息时导致崩溃问题解决
对服务器返回的JSON数据解析时总会出现null数据导致崩溃的现象,避免这种崩溃出现的方法之一就是对返回的数据类型进行判断,但每个返回数据的地方都这么判断的话就会有点繁琐。另一个简单实用的方法就是消息转发:将向NSNull发送的消息转发给可以执行该方法的类。这里通过创建一个NSNull的分类就能实现:
#import "NSNull+signature.h"
#define nullObjects @[@"",@0,@[],@{}] // 执行相关方法的数据类型
@implementation NSNull (signature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
for (NSObject *object in nullObjects) {
signature = [object methodSignatureForSelector:aSelector];
if (signature) {
break;
}
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
for (NSObject *object in nullObjects) {
if ([object respondsToSelector:selector]) {
[anInvocation invokeWithTarget:object];
}
}
// [self doesNotRecognizeSelector:selector]; // 抛出异常
}
@end
#import "ViewController.h"
#import "NSNull+signature.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id nullObject = [NSNull null];
NSString *nullStr = nullObject;
NSUInteger strLength = nullStr.length;
NSLog(@"%lu",(unsigned long)strLength);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
不加分类运行后会抛出以下异常:
-[NSNull length]: unrecognized selector sent to instance 0x195c466e0
加分类后一切正常,输出结果:
2018-02-11 15:41:01.528 [7186:716497] 0
通过消息转发我们很容易就解决了NSNull崩溃的问题,再也不用担心解析服务器数据为null的情况了。
对于消息转发这里只做了简单的应用,想要了解关于消息转发的更多信息可以参考以下两篇博客:
函数调用
Runtime 你为何如此之屌?