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傳參數上報日誌的問題,在此期間學到了不少知識,可能有些寫的不是很正確,望大牛指正。

相關文章
相關標籤/搜索