这两天接了个阿里的面试电话,有一个问题难到了,问了我关于NSInvocation的问题,当时真是不知道,后来赶紧补了下,发现NSInvocation主要还是用来解决多个参数如何传递的问题和直接拿到返回值,
以下是简单的创建方法签名的例子,可以看到既可以从类中获取签名,也可从实例中获取。
SEL initSel = @selector(init);
SEL allocSel = @selector(alloc);
NSMethodSignature *initSig, *allocSig;
//从实例中请求实例方法签名
initSig = [@"String" methodSignatureForSelector:initSel];
//从类中请求实例方法签名
initSig = [NSString instanceMethodSignatureForSelector:initSel];
//从类中请求实例方法签名
allocSig = [NSString methodSignatureForSelector:allocSel];
以下是网上摘抄的一部分代码,试验了下可以达到,但是不知道为什么会崩溃,没找到原因
#import "ViewController.h"
@interface Target : NSObject
@end
@implementation Target
-(NSString *)saySomeThingA:(NSString *)a B:(NSString *)b C:(NSString *)c
{
NSString *string = [NSString stringWithFormat:@"%@ and %@ and %@",a,b,c];
return string;
}
-(id)performSelector:(SEL)aSelector withArguments:(NSArray *)arguments
{
// 方法签名中保存了方法的名称/参数/返回值,协同NSInvocation来进行消息的转发
// 方法签名一般是用来设置参数和获取返回值的, 和方法的调用没有太大的关系
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = aSelector;
// invocation 有2个隐藏参数,所以 argument 从2开始
if ([arguments isKindOfClass:[NSArray class]]) {
//此处不能通过遍历参数数组来设置参数,因为外界传进来的参数个数是不可控的
//因此通过numberOfArguments方法获取的参数个数,是包含self和_cmd的,然后比较方法需要的参数和外界传进来的参数个数,并且取它们之间的最小值
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];
if ([argument isKindOfClass:[NSNull class]]) {
argument = nil;
}
[invocation setArgument:&argument atIndex:2+i];
}
}
}
[invocation invoke];
//判断当前调用的方法是否有返回值
id renturnVal;
if (strcmp(signature.methodReturnType, "@") == 0) {//有返回值
//将返回值赋值给renturnVal
[invocation getReturnValue:&renturnVal];
}
// 需要做返回类型判断。比如返回值为常量需要包装成对象,这里仅以最简单的`@`为例
return renturnVal;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//通过Block调用
id returnVal = invokeBlock((id)^(NSString *a,NSString *b,NSString *c){
return [NSString stringWithFormat:@"%@ and %@ and %@",a,b,c];
},@[@"01",@"02",@"03"]);
NSLog(@"%@",returnVal);
//直接调用
Target *targetnew = [Target new];
NSString *str111 = [targetnew performSelector:@selector(saySomeThingA:B:C:) withArguments:@[@"晓明",@"在吗",@"开门"]];
NSLog(@"%@",str111);
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
static id invokeBlock(id block,NSArray *arguments){
if (block == nil) {return nil;}
id target = [block copy];
const char *_Block_signature(void *);
const char *signature = _Block_signature((__bridge void *)target);
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = target;
// invocation 有1个隐藏参数,所以 argument 从1开始
if ([arguments isKindOfClass:[NSArray class]]) {
//此处不能通过遍历参数数组来设置参数,因为外界传进来的参数个数是不可控的
//因此通过numberOfArguments方法获取的参数个数,是包含self和_cmd的,然后比较方法需要的参数和外界传进来的参数个数,并且取它们之间的最小值
NSInteger count = MIN(arguments.count, methodSignature.numberOfArguments-1);
for (int i = 0; i < count; i++) {
const char *type = [methodSignature getArgumentTypeAtIndex:1+i];
NSString *typeStr = [NSString stringWithUTF8String:type];
// 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
if ([typeStr containsString:@"\""]) {
type = [typeStr substringToIndex:1].UTF8String;
}
// 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
if (strcmp(type, "@") == 0) {
id argument = arguments[i];
[invocation setArgument:&argument atIndex:1 + i];
}
}
}
[invocation invoke];
//判断当前调用的方法是否有返回值
id renturnVal;
const char *type = methodSignature.methodReturnType;
NSString *returnType = [NSString stringWithUTF8String:type];
if ([returnType containsString:@"\""]) {
type = [returnType substringToIndex:1].UTF8String;
}
if (strcmp(type, "@") == 0) {
[invocation getReturnValue:&renturnVal];
}
// 需要做返回类型判断。比如返回值为常量需要包装成对象,这里仅以最简单的`@`为例
return renturnVal;
}
@end