版本: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
@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];