這兩天接了個阿里的面試電話,有一個問題難到了,問了我關于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