Objective-C NSInvocation

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建 NSInvocation 对象,NSMethodSignature 为方法签名类
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
// 获取 NSMethodSignature 对象
@property (readonly, retain) NSMethodSignature *methodSignature;
// 会将传入的所有参数以及 target 都 retain 一遍
- (void)retainArguments;
// 判断参数是否还存在 调用retainArguments之前,值为NO,调用之后值为YES
@property (readonly) BOOL argumentsRetained;
// 消息调用者
@property (nullable, assign) id target;
// 调用的消息
@property SEL selector;
// 获取消息返回值
- (void)getReturnValue:(void *)retLoc;
// 设置消息返回值
- (void)setReturnValue:(void *)retLoc;
// 获取消息参数
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
// 设置消息参数
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
// 发送消息
- (void)invoke;
// target 发送消息
- (void)invokeWithTarget:(id)target;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取方法
SEL sel = NSSelectorFromString(@"method");
// 创建方法签名
NSMethodSignature *sig = [target methodSignatureForSelector:sel];
// 根据方法签名创建调用 invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
// 设置消息调用者
[invocation setTarget:target];
// 设置调用的消息
[invocation setSelector:sel];
// 如果有参数,设置消息参数
int arg = 1;
[invocation setArgument:&arg atIndex:2];
// 发送消息
[invocation invoke];

// 其它操作 获取返回值等等

注意事项

setArgument

objc_msgSend 是一个参数个数可变的函数,第一个参数代表接受者,第二个参数代表选择子,后续参数就是消息中的那些参数

1
void objc_msgSend(id self, SEL cmd, ...);

invoke就是调用objc_msgSend发送消息,setArgument:atIndex:是设置消息参数,所以

1
2
3
4
5
6
// 设置 target、selector
[invocation setTarget:target];
[invocation setSelector:sel];
// 相当于
[invocation setArgument:&target atIndex:0];
[invocation setArgument:&selector atIndex:1];

当传入其它参数时,atIndex一定是从2开始

getReturnValue

当没有返回值时,不可以使用getReturnValue

1
2
3
// 获取消息返回值
id returnValue = nil;
[invocation getReturnValue:&returnValue];

以上代码在大多数情况下会 Crash,返回值可分为两种类型

  • 返回值是对象
    由于returnValue__strong,所以 ARC 假定放入的变量已被保留,在超出作用范围时会释放它,但在这种情况下并不是这样,所以会造成 Crash

  • 返回值基本类型
    需要申请内存地址,将返回值放入内存缓冲区,否则 Crash

返回值是对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (NSDictionary *)hello {
...
return @{@"key": @"value"};
}

SEL sel = NSSelectorFromString(@"hello");
NSMethodSignature *sig = [target methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];

[invocation setTarget:target];
[invocation setSelector:sel];
[invocation invoke];

// 方法 1
id returnValue = nil;
void *tempResult = nil;
[invocation getReturnValue:&tempResult];
returnValue = (__bridge id)tempResult;

// 方法 2
__autoreleasing id returnValue = nil;
[invocation getReturnValue:&returnValue];

// 方法 3
__unsafe_unretained id returnValue = nil;
[invocation getReturnValue:&returnValue];

返回值是基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (NSInteger)hello {
...
return 1;
}

SEL sel = NSSelectorFromString(@"hello");
NSMethodSignature *sig = [target methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];

[invocation setTarget:target];
[invocation setSelector:sel];
[invocation invoke];

id returnValue = nil;
NSUInteger length = [sig methodReturnLength];
void *buffer = (void *)malloc(length);
[invocation getReturnValue:buffer];
// 返回值是 NSInteger
returnValue = [NSNumber numberWithInteger:*((NSInteger *)buffer)];
// 返回值是 BOOL
returnValue = [NSNumber numberWithBool:*((BOOL *)buffer)];
// 根据返回值类型(👇下面有)存放到 NSValue 中
returnValue = [NSValue valueWithBytes:buffer objCType:returnType];

不产生 Crash 情况

列举一种,应该还有其它情况

1
2
3
4
- (NSString *)hello {
...
return @"hello";
}

编译器在编译时应该做了某种优化,具体不清楚

NSMethodSignature

通过 methodReturnLength methodReturnType 判断有无返回值及返回值类型

API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 根据给定的 Objective-C 方法类型返回 NSMethodSignature 对象
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
// 参数个数
@property (readonly) NSUInteger numberOfArguments;
// 获取指定索引处的参数类型
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;
// 所有参数所占用堆空间大小
@property (readonly) NSUInteger frameLength;
// Whether the receiver is asynchronous when invoked through distributed objects.
- (BOOL)isOneway;
// 返回值类型
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
// 返回值长度
@property (readonly) NSUInteger methodReturnLength;

判断返回值类型

1
2
3
4
5
6
7
8
const char *returnType = sig.methodReturnType;
NSUInteger returnLength = sig.methodReturnLength;
// 无返回值时,长度为 0
if (!returnLength) {...}
// 判断是否是对象
if (!strcmp(returnType, @encode(id))) {...}
// 判断是否是 BOOL 类型
if (!strcmp(returnType, @encode(BOOL))) {...}