【动态调用】
在项目开发的时候,我们经常会遇到一种情况,就是如何在判断是否包含某个类,如果存在则进行调用这个类的某个方法。简单来说就是动态调用方法了。
在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个缺点:
- 参数必须是NSObject,对于一些函数携带的参数是基本类型(如int、bool)等方法的时候,就很难搞了。
- 参数最多是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