我为什么会写这篇文章
今天在写Bugly上报错误的时候遇到了一个问题,由于项目使用组件化开发思想,在上报错误组件里需要知道当前是否导入了Bugly组件,如果没有Bugly组件,则不进行Bugly上报,我第一个想到了用
NSClassFromString
来判断是否存在Bugly,如果存在,再使用performSelector:withObject
在runtime
调用Bugly上报方法,但是Bugly输出日志的方法BuglyLog level:<#(BuglyLogLevel)#> log:<#(NSString *), ...#>
第一个参数需要传入NSUInterger
类型的基本数据,但是performSelector:withObject
方法只能传入OC对象,所以我在解决问题的同时顺带研究了一下OC执行方法,消息传递的几种常见方法。
第一种方式
performSelector方式
这应该是开发过程中动态执行方法最常用的方式了吧,最常见的用法就是performSelector:withObject
,或者可以再带一个参数performSelector:withObject:withObject:
,就像这样调用:
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对象
第二种方式
NSInvocation方式
先不说啥,直接上代码
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选择的方法,然后调用这个函数执行,我试了下,能完美解决文章开头我遇到的问题
继续探索
第四种方式
最底层的消息传递方式
上代码
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传参数上报日志的问题,在此期间学到了很多知识,可能有些写的不是很正确,望大牛指正。