关于NSInvocation的问题

这两天接了个阿里的面试电话,有一个问题难到了,问了我关于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




你可能感兴趣的:(关于NSInvocation的问题)