今天在寫Bugly上報錯誤的時候遇到了一個問題,因爲項目使用組件化開發思想,在上報錯誤組件裏須要知道當前是否導入了Bugly組件,若是沒有Bugly組件,則不進行Bugly上報,我第一個想到了用
NSClassFromString
來判斷是否存在Bugly,若是存在,再使用performSelector:withObject
在runtime
調用Bugly上報方法,可是Bugly輸出日誌的方法BuglyLog level:<#(BuglyLogLevel)#> log:<#(NSString *), ...#>
第一個參數須要傳入NSUInterger
類型的基本數據,可是performSelector:withObject
方法只能傳入OC對象,因此我在解決問題的同時順帶研究了一下OC執行方法,消息傳遞的幾種常見方法。bash
這應該是開發過程當中動態執行方法最經常使用的方式了吧,最多見的用法就是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;
複製代碼
1.不能傳超過三個參數 2.參數只能爲OC對象ui
先不說啥,直接上代碼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等等,會在運行時找到目標發送消息- 注意這裏傳的參數是地址,因此還不不知足咱們的需求
搜索中... 搜索中... 搜索中... 找到方案了!線程
上代碼指針
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傳參數上報日誌的問題,在此期間學到了不少知識,可能有些寫的不是很正確,望大牛指正。