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執行方法,消息傳遞的幾種常見方法。

第一種方式

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。