NSInvocation的基本使用

文章目录
  1. 1. 方法签名
  2. 2. 使用NSInvocation发送消息
  3. 3. SensorsAnalytics 中NSInvocation的应用

NSInvocation的基本使用

iOS中可以间接向对象发送消息的方式有两种(我知道的):

  1. 使用performSelector:withObject;
  2. 使用NSInvocation。

performSelector:withObject能完成简单的调用。但是对于大于2个的参数或者有返回值的消息,它就显得有点有心无力了,那么在这种情况下,我们就可以使用NSInvocation来进行这些相对复杂的操作。

方法签名

1
An NSMethodSignature object records type information for the return value and parameters of a method. It is used to forward messages that the receiving object does not respond to—most notably in the case of distributed objects.

NSMethodSignature对象记录了方法的参数和返回值的类型信息。它被用于转发接受者不能处理的消息。

1
You typically create an NSMethodSignature object using the NSObject methodSignatureForSelector: instance method . It is then used to create an NSInvocation object

NSObject的实例方法methodSignatureForSelector:是创建NSMethodSignature对象的典型方法。 NSMethodSignature对象可用于创建NSInvocation 对象(通过NSInvocation的invocationWithMethodSignature:类方法)。

下面是创建方法签名的代码:

1
2
3
4
5
///1. 创建方法签名
NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(fucWithName:)];
if (!signature) {
return;
}

使用numberOfArguments获取方法的参数个数,参数个数比sel描述的参数多两个隐藏参数,一个是self,一个是_cmd。

NSMethodSignature的主要方法:

  1. getArgumentTypeAtIndex:获取参数
  2. methodReturnType获取返回类型。

使用NSInvocation发送消息

1
2
3
4
5
6
7
8
///2、创建NSInvocation对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
//注意:这里的方法名一定要与方法签名类中的方法一致
invocation.selector = @selector(fucWithName:);
NSString *strName = @"dog";
//这里的Index要从2开始,以为0、1已经被占据了,分别是self(target),selector(_cmd)
[invocation setArgument:&strName atIndex:2];
1
2
3
4
5
6
7
8
//3、调用invoke方法
[invocation invoke];

id res = nil;
if (signature.methodReturnLength != 0) {
[invocation getReturnValue:&res];
}
NSLog(@"res = %@",res);

首先使用invocationWithMethodSignature:创建一个NSInvocation对象,然后设置对象的target、selector。NSInvocation对象实际上就是将方法封装为对象。然后使用invoke方法调用消息。使用getReturnValue获取返回值。输出的结果如下:

1
2
ViewController object receive message functionWithName:
res = dog

SensorsAnalytics 中NSInvocation的应用

SensorsAnalytics 对NSInvocation做了扩展,能将为原始类型的参数(int、char、bool)做兼容 ,能将原始类型的返回值转化为对象类型 。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (void)sa_setArgument:(id)argumentValue atIndex:(NSUInteger)index
{
//获取参数的类型
const char *argumentType = [self.methodSignature getArgumentTypeAtIndex:index];
//如果参数类型是原生类型,解包参数
if ([argumentValue isKindOfClass:[NSNumber class]] && strlen(argumentType) == 1) {
// Deal with NSNumber instances (converting to primitive numbers)
NSNumber *numberArgument = argumentValue;

MPObjCNumericTypes arg;
switch (argumentType[0])
{
case _C_CHR: arg._chr = [numberArgument charValue]; break;
case _C_UCHR: arg._uchr = [numberArgument unsignedCharValue]; break;
case _C_SHT: arg._sht = [numberArgument shortValue]; break;
case _C_USHT: arg._usht = [numberArgument unsignedShortValue]; break;
case _C_INT: arg._int = [numberArgument intValue]; break;
case _C_UINT: arg._uint = [numberArgument unsignedIntValue]; break;
case _C_LNG: arg._lng = [numberArgument longValue]; break;
case _C_ULNG: arg._ulng = [numberArgument unsignedLongValue]; break;
case _C_LNG_LNG: arg._lng_lng = [numberArgument longLongValue]; break;
case _C_ULNG_LNG: arg._ulng_lng = [numberArgument unsignedLongLongValue]; break;
case _C_FLT: arg._flt = [numberArgument floatValue]; break;
case _C_DBL: arg._dbl = [numberArgument doubleValue]; break;
case _C_BOOL: arg._bool = [numberArgument boolValue]; break;
default:
NSAssert(NO, @"Currently unsupported argument type!");
}
//将原始类型参数设置Invocation中
[self setArgument:&arg atIndex:(NSInteger)index];
}
else if ([argumentValue isKindOfClass:[NSValue class]])
{
NSValue *valueArgument = argumentValue;

NSAssert2(strcmp([valueArgument objCType], argumentType) == 0, @"Objective-C type mismatch (%s != %s)!", [valueArgument objCType], argumentType);

void *buffer = SAAllocBufferForObjCType([valueArgument objCType]);

[valueArgument getValue:buffer];

[self setArgument:&buffer atIndex:(NSInteger)index];

SAFree(buffer);
} else {
switch (argumentType[0])
{
case _C_ID:
{
[self setArgument:&argumentValue atIndex:(NSInteger)index];
break;
}
case _C_SEL:
{
SEL sel = NSSelectorFromString(argumentValue);
[self setArgument:&sel atIndex:(NSInteger)index];
break;
}
default:
NSAssert(NO, @"Currently unsupported argument type!");
}
}
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//将原始类型的参数封装为对象类型
- (id)sa_returnValue
{
__strong id returnValue = nil;

NSMethodSignature *methodSignature = self.methodSignature;

const char *objCType = [methodSignature methodReturnType];

void *buffer = SAAllocBufferForObjCType(objCType);

[self getReturnValue:buffer];

if (strlen(objCType) == 1) {
switch (objCType[0])
{
case _C_CHR: returnValue = @(*((char *)buffer)); break;
case _C_UCHR: returnValue = @(*((unsigned char *)buffer)); break;
case _C_SHT: returnValue = @(*((short *)buffer)); break;
case _C_USHT: returnValue = @(*((unsigned short *)buffer)); break;
case _C_INT: returnValue = @(*((int *)buffer)); break;
case _C_UINT: returnValue = @(*((unsigned int *)buffer)); break;
case _C_LNG: returnValue = @(*((long *)buffer)); break;
case _C_ULNG: returnValue = @(*((unsigned long*)buffer)); break;
case _C_LNG_LNG: returnValue = @(*((long long *)buffer)); break;
case _C_ULNG_LNG: returnValue = @(*((unsigned long long*)buffer)); break;
case _C_FLT: returnValue = @(*((float *)buffer)); break;
case _C_DBL: returnValue = @(*((double *)buffer)); break;
case _C_BOOL: returnValue = @(*((_Bool *)buffer)); break;
case _C_ID: returnValue = *((__unsafe_unretained id *)buffer); break;
case _C_SEL: returnValue = NSStringFromSelector(*((SEL *)buffer)); break;
default:
NSAssert1(NO, @"Unhandled return type: %s", objCType);
break;
}
} else {
switch (objCType[0])
{
case _C_STRUCT_B: returnValue = [NSValue valueWithBytes:buffer objCType:objCType]; break;
case _C_PTR:
{
CFTypeRef cfTypeRef = *(CFTypeRef *)buffer;
if ((strcmp(objCType, @encode(CGImageRef)) == 0 && CFGetTypeID(cfTypeRef) == CGImageGetTypeID()) ||
(strcmp(objCType, @encode(CGColorRef)) == 0 && CFGetTypeID(cfTypeRef) == CGColorGetTypeID()))
{
returnValue = (__bridge id)cfTypeRef;
} else {
NSAssert(NO, @"Currently unsupported return type!");
}
break;
}
default:
NSAssert1(NO, @"Unhandled return type: %s", objCType);
break;
}
}

SAFree(buffer);

return returnValue;
}