【发烂渣】iOS动态调用方法 -- NSInvocation

2,815 阅读4分钟

【动态调用】

在项目开发的时候,我们经常会遇到一种情况,就是如何在判断是否包含某个类,如果存在则进行调用这个类的某个方法。简单来说就是动态调用方法了。

在Objective-C中,其实提供了几种方式进行动态调用方法。

【我已经进行了一个封装,如果想直接开箱使用,可以通过下载、cocoapod进行使用】GitHub地址

第一种方式 performSelector:withObject

这个也是我们在开发中最经常使用的动态调用函数。并且提供了多种方式:

//不带参数函数调用
- (id)performSelector:(SEL)aSelector;

//带一个参数的函数的调用
- (id)performSelector:(SEL)aSelector withObject:(id)object;

//带两个参数的函数的调用
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

//延迟调用函数,并且携带一个参数
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

//在指定的线程进行调用
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait

这些这是部分的使用方法,查看更多,请阅读官方文档(就是懒,不想全部一一说明白,而且这个也不是本文的重点)

多说一点,就是performSelector有一个配套的方法cancelPreviousPerformRequestsWithTarget,可以在指定的时间内取消上一次的调用。

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;

【缺点performSelector】 这个方式主要有2个缺点:

  1. 参数必须是NSObject,对于一些函数携带的参数是基本类型(如int、bool)等方法的时候,就很难搞了。
  2. 参数最多是2个。如果一个函数的参数有3个或者3个以上的时候,也会十分的难搞。

第二种方式:objc_msgSend

对于这个就涉及到比较底层的东西。而且如果要拿出来说,估计要写很久。所以这个我们留带下一篇进行讲解。

第三种方式: NSInvocation

NSInvocation 也是这次要重点讲解的地方.

【简介】

NSInvocation简单的来说,就是把我们需要动态调用的函数的名称、参数、返回值封装起来,变成一个对象进行调用。

【关于NSMethodSignature】

在使用NSInvocation之前我们必须获取到方法的返回值类型和参数类型信息。 所以可以通过NSMethodSignature来获取到这部分的信息。废话不多说,上代码:

【NSInvocation的基本使用】

- (void)test:(NSString *)helloWorld{
NSLog(@"这是一个测试动态调用的方法");
}

//方法签名类的使用
NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(test:)];

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//设置方法调用者
invocation.target = self;
//注意:这里的方法名一定要与方法签名类中的方法一致
invocation.selector = @selector(test:);
NSString *str = @"helloWorld";
//需要注意的是!index下标是从2开始的。因为0为【self(target)】,而1为【selector(_cmd)】
[invocation setArgument:&helloWorld atIndex:2];
//调用
[invocation invoke];

以上就是NSInvocation的最基本的使用了。但是如果单纯这样写,我们在项目会遇到各种BUG。

【函数是否存在的判断】

	NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(test:)];
	NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
 	if (!inv){
       //如果需要在项目中抛出异常则为如下:
        //[self doesNotRecognizeSelector:sel];
        //否则直接返回nil即可
        return nil;
    }

【函数参数数量是否一致】

NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(test:)];
if(signature.numberOfArguments == outerArguments.count + 2){
	//signature.numberOfArgument代表是方法签名类中,找到该方法所需要的参数数量
    //outerArguments为开发者外部传人的参数数量
    //+2 是因为signature.numberOfArgument里面包含了【target】和【_cmd】
}

【函数是否有返回值】

NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(test:)];
id returnValue = nil;
if (signature.methodReturnLength != 0) {//有返回值
	//这一行是进行赋值操作,把返回值设置到【returnValue】
    [invocation getReturnValue:&returnValue];
}
return returnValue;

【判断返回值的类型】

NSMethodSignature  *signature = [[self class] instanceMethodSignatureForSelector:@selector(test:)];
const char *returnType = signature.methodReturnType;

【参数为基础类型的(bool、int等)】

我们可以通过Type Encodings进行转换。

Type Encodings 简单的来说,就是用一个C类型的字符串来表达一个数据类型。我们可以通过@encode来获取到这个类型的C字符串。

具体的编码可以参考这个文章【 编码类型

也可以自行使用代码去获取。代码如下:

int main(int argc, const char * argv[]) {
   char *charC = @encode(int);
   NSLog(@"%s",charC);
  return 0;
}
结果为:
i