iOS关于动态执行方法的探索-performSelect-NSInvocation-objc_msgSend

1.jpg

我为何会写这篇文章

今天在写Bugly上报错误的时候遇到了一个问题,因为项目使用组件化开发思想,在上报错误组件里须要知道当前是否导入了Bugly组件,若是没有Bugly组件,则不进行Bugly上报,我第一个想到了用NSClassFromString来判断是否存在Bugly,若是存在,再使用performSelector:withObjectruntime调用Bugly上报方法,可是Bugly输出日志的方法BuglyLog level:<#(BuglyLogLevel)#> log:<#(NSString *), ...#>第一个参数须要传入NSUInterger类型的基本数据,可是performSelector:withObject方法只能传入OC对象,因此我在解决问题的同时顺带研究了一下OC执行方法,消息传递的几种常见方法。bash

第一种方式

performSelector方式

这应该是开发过程当中动态执行方法最经常使用的方式了吧,最多见的用法就是performSelector:withObject,或者能够再带一个参数performSelector:withObject:withObject:,就像这样调用:app

id BuglyClass = (id)NSClassFromString(@"Bugly");
NSError *error = [NSError errorWithDomain:errorTitle code:-1 userInfo:@{NSLocalizedDescriptionKey:@""}];
[BuglyClass performSelector:@selector(reportError:) withObject:error];
复制代码

也有延时触发的用法函数

performSelector:withObject:afterDelay:
复制代码

在某个线程中执行的用法,固然这些不是本文讨论的重点,因此一笔带过组件化

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
复制代码
其实不难发现performSelector方式有一些弊端

1.不能传超过三个参数 2.参数只能为OC对象ui

第二种方式

NSInvocation方式

先不说啥,直接上代码spa

NSString *string = @"test";
NSNumber *number = [NSNumber numberWithInt:1];

SEL selector = @selector(function2:count:);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

invocation.target = self;
invocation.selector = selector;
// 第一和第二个参数是target和selector
[invocation setArgument:&string atIndex:2];
[invocation setArgument:&number atIndex:3];
//执行
[invocation invoke];
复制代码
  • NSMethodSignature: 方法签名,若是方法不存在或者参数对不上,会直接crach,其实我也不是很理解这个签名的意思,多是代码写得不够多吧,按照个人理解,这个数字签名就像是找一下这个target里面是否有这个方法,参数是否正确,若是都对,则签名生效,不然就可能奔溃
  • NSInvocation: 包装了一次消息传递的全部内容,包括target,select,argument等等,会在运行时找到目标发送消息
  • 注意这里传的参数是地址,因此还不不知足咱们的需求

为了解决今天上报Bugly的问题,我还得继续探索

搜索中... 搜索中... 搜索中... 找到方案了!线程

第三种方式

函数指针方式

上代码指针

id target = self;
SEL selector = @selector(function:count:);
// 第一个第二个参数是self和selector
typedef void (*function)(id, SEL, NSString *, int);
function methodToCall = (function)[target methodForSelector:selector];
methodToCall(target, selector, @"string",1);
复制代码

可能有些小伙伴没有见过这种用法,我其实也是解决Bugly问题的时候才了解到的,不过看代码应该是能看懂的,首先用selector找到要调用的方法,再定义一个函数指针,指向selector选择的方法,而后调用这个函数执行,我试了下,能完美解决文章开头我遇到的问题日志

继续探索

第四种方式

最底层的消息传递方式

上代码code

id BuglyLogClass = (id)NSClassFromString(@"BuglyLog");
SEL selector = @selector(level:log:);
void (*logAction)(id, SEL, NSUInteger,NSString *) = (void (*)(id, SEL, NSUInteger,NSString *))objc_msgSend;
logAction(BuglyLogClass, selector, 1,@"description");
复制代码

这是我最后使用的方法。全部target执行方法在底层都是经过objc_msgSend消息传递实现的,要执行的方法归根结底就是一条消息,发送给目标target,咱们来看一下Apple官方的解释

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */
void objc_msgSend(void /* id self, SEL op, ... */ )
复制代码

其实performSelector的底层实现也是调用了objc_msgSend实现消息发送

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}
复制代码

研究到这里,我已经解决了个人Bugly传参数上报日志的问题,在此期间学到了不少知识,可能有些写的不是很正确,望大牛指正。

相关文章
相关标签/搜索