编码篇 - NSInvocation的简单使用
前言
在认识 NSInvocation 之前,iOS开发中我们一般会使用以下两种方式去调用一个方法
(1) [obj methodName];
(2) [obj performSelector......];
但是我们知道,这两种方式都有各自的缺陷:
第一种的方法:无法通过方法名字符串来调用方法;
第二种方式:不能执行参数过多的方法,一般参数数量不能超过2个就很麻烦处理了。
那么有没有一种方式,既可以通过方法名字符串来调用方法,也可以传递很多参数呢?答案肯定是有的,那就是 NSInvocation 。NSInvocation可以处理很多参数、返回值。会java的人都知道反射操作,其实NSInvocation就相当于反射操作。
NSInvocation
文档描述
An NSInvocation is an Objective-C message rendered static, that is, it is an action turned into an object. NSInvocation objects are used to store and forward messages between objects and between applications, primarily by NSTimer objects and the distributed objects system.
NSInvocation可以去构建一个方法然后转发这个方法,所以我们可以利用这两点特性去做一些事情。
常见方法及属性
NSInvocation其他常见方法属性
//保留参数,它会将传入的所有参数以及target都retain一遍
- (void)retainArguments
// 判断参数是否存在,调用retainArguments之前,值为NO,调用之后值为YES
@property (readonly) BOOL argumentsRetained;
// 设置消息返回值
- (void)setReturnValue:(void *)retLoc;
NSMethodSignature其他常见方法属性
// 方法参数个数
@property (readonly) NSUInteger numberOfArguments;
// 获取方法的长度
@property (readonly) NSUInteger frameLength;
// 是否是单向
- (BOOL)isOneway;
// 获取方法返回值类型
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
// 获取方法返回值的长度
@property (readonly) NSUInteger methodReturnLength;
NSInvocation的使用步骤
(1).指定一个 SEL
(2).根据这个 SEL 创建 NSMethodSignature
(3).根据这个 NSInvocation 创建一个NSInvocation对象
(4).设置这个 invocation 的 target、selector、参数 、返回值
(5).调用NSInvocation对象的invoke方法
下面是一个具体的使用实例:
NSString *methodNameStr = @"test:withArg2:";
SEL selector = NSSelectorFromString(methodNameStr);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
//此时我们应该判断方法是否存在,如果不存在这抛出异常
if (signature == nil) {
//aSelector为传进来的方法
NSString *info = [NSString stringWithFormat:@"%@方法找不到", NSStringFromSelector(selector)];
[NSException raise:@"方法调用出现异常" format:info, nil];
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
NSString *arg1 = @"arg1";
NSString *arg2 = @"arg2";
[invocation setArgument:&arg1 atIndex:2];
[invocation setArgument:&arg2 atIndex:3];
[invocation invoke];
if (signature.methodReturnLength>0) {
id returnResult = nil;
[invocation getReturnValue:&returnResult];
NSLog(@"%@",returnResult);
}
- (NSString *)test:(NSString *)ceshiStr withArg2:(NSString *)arg2
{
NSLog(@"%@----%@",ceshiStr,arg2);
return @"返回值";
}
PS:如果想根据参数数组,动态设置参数
// invocation 有2个隐藏参数,所以 argument 从2开始
if ([arguments isKindOfClass:[NSArray class]]) {
NSInteger count = MIN(arguments.count, signature.numberOfArguments - 2);
for (int i = 0; i < count; i++) {
const char *type = [signature getArgumentTypeAtIndex:2 + i];
// 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
if (strcmp(type, "@") == 0) {
id argument = arguments[i];
[invocation setArgument:&argument atIndex:2 + i];
}
}
}
页面强制旋转中用到的 NSInvocation
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = UIInterfaceOrientationLandscapeRight;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}