我為什么會寫這篇文章
今天在寫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傳參數上報日志的問題,在此期間學到了很多知識,可能有些寫的不是很正確,望大牛指正。