NSInvocation的使用

版本:iOS13.6

一、简介

通常调用方法的方式是使用[实例 方法名][实例 方法名:参数]

[self methodName];
或
[self methodName:array];

若该方法没有公开,可以使用NSObject的performSelector方法,但performSelector只支持调用最多两个入参且入参类型和返回类型为id的方法。

id returnValue = [self performSelector:@selector(methodName)];
或
id returnValue = [self performSelector:@selector(methodName) withObject:@"object1"];
或
id returnValue = [self performSelector:@selector(methodName) withObject:@"object1" withObject:@"object2"];

若入参的个数多于两个,可以使用NSInvocation来调用方法。

二、NSInvocation的API

@interface NSInvocation : NSObject

//根据方法签名来初始化实例对象
//方法签名 可查看第三节
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
//对象的方法签名 只读
@property (readonly, retain) NSMethodSignature *methodSignature;
//强引用传入的参数,防止参数被释放
- (void)retainArguments;
//当前的参数是否为强引用 只读
@property (readonly) BOOL argumentsRetained;
//调用该方法的对象
@property (nullable, assign) id target;
//要调用的方法的选择器 
@property SEL selector;

//获取该方法的返回值
//retLoc 一个变量的地址,该变量会保存返回值
- (void)getReturnValue:(void *)retLoc;
//设置该方法的返回值,虽然方法会调用,但返回值则会被该值替换
//retLoc 一个变量的地址,该变量的值即为要设置的返回值
- (void)setReturnValue:(void *)retLoc;

//获取该方法对应索引的参数值
//argumentLocation 一个变量的地址,该变量会保存参数的值
//idx 第几个参数 从2开始 前两个分别被该方法的self与_cmd占用
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
//设置该方法对应索引的参数值
//argumentLocation 一个变量的地址,该变量的值即为要设置的参数值
//idx 第几个参数 从2开始 前两个分别被该方法的self与_cmd占用
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

//调用
- (void)invoke;
//调用 会替换属性target
- (void)invokeWithTarget:(id)target;

@end

三、NSMethodSignature的API

可通过NSObject的实例方法methodSignatureForSelector和类方法instanceMethodSignatureForSelector来创建方法签名。

NSMethodSignature *signature = [self methodSignatureForSelector:NSSelectorFromString(@"methodName")];
或
NSMethodSignature *signature = [self.class instanceMethodSignatureForSelector:NSSelectorFromString(@"")];
@interface NSMethodSignature : NSObject

//通过方法的类型字符串初始化实例
//类型字符串 可查看第四节
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
//该方法的参数数量,包括方法自带的self和_cmd
@property (readonly) NSUInteger numberOfArguments;
//获取对应索引的参数类型字符串
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx;
//该方法所占的字节数
@property (readonly) NSUInteger frameLength;
//是否为单向(不知是何意)
- (BOOL)isOneway;

//该方法的返回类型字符串
@property (readonly) const char *methodReturnType;
//该方法的返回类型值所占的字节数
@property (readonly) NSUInteger methodReturnLength;

@end

四、类型字符串

可通过@encode(type)来获取类型字符串
例如:
@encode(NSString)的类型字符串为@
@encode(NSInteger)的类型字符串为q
@encode(double)的类型字符串为d
具体的可查看下面的枚举,虽然被废弃了,但大体没有变化。

enum _NSObjCValueType {
    NSObjCNoType = 0,
    NSObjCVoidType = 'v',
    NSObjCCharType = 'c',
    NSObjCShortType = 's',
    NSObjCLongType = 'l',
    NSObjCLonglongType = 'q',
    NSObjCFloatType = 'f',
    NSObjCDoubleType = 'd',
    NSObjCBoolType = 'B',
    NSObjCSelectorType = ':',
    NSObjCObjectType = '@',
    NSObjCStructType = '{',
    NSObjCPointerType = '^',
    NSObjCStringType = '*',
    NSObjCArrayType = '[',
    NSObjCUnionType = '(',
    NSObjCBitfield = 'b'
}API_DEPRECATED

上面是单个变量的类型字符串,但NSMethodSignature的初始化方法signatureWithObjCTypes需要传入整个方法的类型字符串,具体是怎样的呢?

有一个方法

- (NSString *)getAdressByName:(NSString *)name byAge:(NSInteger)age {
    NSLog(@"name = %@ age = %ld", name, age);
    return @"cd";
}

可使用methodSignatureForSelector来获取该方法的签名

NSMethodSignature *signature = [self methodSignatureForSelector:@selector(getAdressByName:byAge:)];

断点后,可看到有一个参数_typeString值为@32@0:8@16q24

image.png

@32表示方法的返回类型NSString *
@0和:8表示方法自带的参数self_cmd
@16表示自己设置的参数(NSString *)name
q24表示自己设置的参数(NSInteger)age
所以使用signatureWithObjCTypes初始化实例,可以照下面所示来初始化。

NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@32@0:8@16q24"];
也可以
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@@:@q"];

    SEL sel = @selector(getAdressByName:byAge:);
    //通过NSObject的实例方法来获取方法签名
    NSMethodSignature *signature = [self methodSignatureForSelector:sel];
    //通过NSObject的类方法来获取方法签名
//    NSMethodSignature *signature = [self.class instanceMethodSignatureForSelector:sel];
    //通过方法类型字符串来获取方法签名
//    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@32@0:8@16q24"];
    //通过方法类型字符串来获取方法签名
//    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@@:@q"];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    //防止参数被释放
    [invocation retainArguments];
    //方法调用的对象是self
    invocation.target = self;
    //方法的选择器
    invocation.selector = sel;
    //设置第2个参数的值 第0、1参数被方法的self与_cmd占用
    NSString *name = @"zhangsan";
    [invocation setArgument:&name atIndex:2];
    //设置第3个参数的值
    NSInteger age = 20;
    [invocation setArgument:&age atIndex:3];
    [invocation invoke];
    
    //获取方法的返回值 该方法需要在invoke之后调用,否则是nil
    NSString *returnVlaue;
    [invocation getReturnValue:&returnVlaue];
    //获取第2个参数值
    NSString *argument2;
    [invocation getArgument:&argument2 atIndex:2];
    //获取第3个参数值
    NSInteger argument3;
    [invocation getArgument:&argument3 atIndex:3];
    NSLog(@"%@ %@ %ld", returnVlaue, argument2, argument3);

- (NSString *)getAdressByName:(NSString *)name byAge:(NSInteger)age {
    NSLog(@"name = %@ age = %ld", name, age);
    return @"cd";
}
输出:
name = zhangsan age = 20
cd zhangsan 20

若将下面代码放入invoke前面,可使该方法的返回值变为 成都

    //设置返回值
    NSString *setReturnVlaue = @"成都";
    [invocation setReturnValue:&setReturnVlaue];

本文参考iOS - NSInvocation的使用

你可能感兴趣的:(NSInvocation的使用)