iOS 底層 - performSelector:應用

本文源自本人的學習記錄整理與理解,其中參考閱讀了部分優秀的博客和書籍,盡量以通俗簡單的語句轉述。引用到的地方如有遺漏或未能一一列舉原文出處還望見諒與指出,另文章內容如有不妥之處還望指教,萬分感謝 !

對于performSelector:方法作為IOS開發者來說肯定不陌生 !下面就詳細的了解一下它吧 !

performSelecor響應了OC語言的動態性: 延遲到運行時才綁定方法。

本人把 performSelector: 開頭的方法分為兩類:

  • 不需要延遲執行
    • 需要到其他線程執行,線程通信;依賴Runloop
    • 不需要到其他線程執行
  • 需要延遲執行 -- > 傳遞 afterDelay值
    • 不需要到其他線程執行
      兩者的最大區別:
  • 需要延遲執行,就必須添加到RunLoop中才能夠成功執行
  • 不需要延遲執行,除了線程間通信其他不需要添加到Runloop,因為其底層只是一個單純的消息發送;runtime的API -- > objc_msgSend就可以實現。

不需要延遲執行

需要到其他線程執行

//到主線程執行任務
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

//到子線程執行任務
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

示例代碼

aSelector: 方法名
onThread: 需要訪問的線程
withObject: 實現SEL方法時傳遞的參數
waitUntilDone:   
- 設置為YES: 表示需要等當前方法選擇器中的方法執行完再執行下面的代碼,防止還沒停止當前實例對象就先釋放掉了。
- 設置為NO:表示不需要等當前方法選擇器中的方法執行完,可以繼續執行下面的代碼

  [obj performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];

不需要到其他線程執行

[obj performSelector:@selector(test)];
[obj performSelector:@selector(test:) withObject:@"逍遙侯"];
[obj performSelector:@selector(test:with:) withObject:@"龍門逍遙侯" withObject:@"XYH"];
  • performSelector: withObject 底層源碼
- (id)performSelector:(SEL)sel withObject:(id)obj {
   //判斷如果方法為空,直接拋出方法不存在異常錯誤
    if (!sel) [self doesNotRecognizeSelector:sel]; 
    //objc_msgSend消息發送
    return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}


重點:performSelector:withObject:只是一個單純的消息發送,和時間沒有一點關系。所以不需要添加到子線程的Runloop中也能執行。

編譯階段并不會去檢查方法是否有效存在,只會給出警告:

Undeclared selector ''

如果要執行的方法名也是動態不確定的一個參數:

 [obj performSelector:selector];

編譯器也只會提示說因為當前方法名未知可能會引起內存泄露相關問題:

PerformSelector may cause a leak because its selector is unknown

所以在實際開發中,為了避免運行時突然報錯找不到方法等問題,可以少使用performSelector方法。

需要延遲執行

不需要到其他線程執行

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

簡單示例

[obj performSelector:@selector(test) withObject:@"逍遙侯" afterDelay:3.f];

performSelector:withObject: afterDelay: 底層調用是把test添加當前線程RunLoop的定時任務(Timer)中3.0秒后調objc_msgSend(self,@selector(test))方法,添加到Timer再執行。如果當前線程是子線程且沒有開啟RunLoop,那么test方法將不會被執行 !

如何在不使用GCD和NSOperation的情況下,實現異步線程?

使用NSThread實現、performSelector:onThread:、performSelectorInBackground 后臺執行;

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{

        NSLog(@"block中的線程 ---- %@",[NSThread currentThread]);
}];

① performSelectorInBackground 開啟新的線程在后臺執行test方法

 [self performSelectorInBackground:@selector(test) withObject:nil];

②performSelector:onThread:在指定線程執行

[self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];

performSelector如何進行多值傳輸?

問題一聽馬上就能回答使用NSArray或者NSDictionary或者自定義Model的形式,但是我查到了一個很妙的方法:

因為在OC中調用一個方法實際上就是發送消息objc_msgSend:

- (void) test {
    NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"謝華華",@"亞呼呼"];

    SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];

    ((void(*)(id,SEL,NSNumber*,NSString*,NSString*,NSArray*)) objc_msgSend)(self,selector,age,name,gender,friends);
}

- (void)getAge:(NSNumber *)age name:(NSString *)name gender:(NSString *)gender friends:(NSArray *)friends
{
    NSLog(@"%d----%@---%@---%@",[age intValue],name,gender,friends[0]);
}

導入#import <objc/message.h>即可。但是這種方式并不是oc封裝的方法所以使用十分的不方便。

網上的第二種方法其實也是以NSArray的形式傳值,然后創建NSInvocation的方式,將參數一一綁定。

-(id)performSelector:(SEL)aSelector withObject:(NSArray *)object
{
    //獲得方法簽名
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
    
    if (signature == nil) {
        return nil;
    }
    
    //使用NSInvocation進行參數的封裝
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = aSelector;
    
    //減去 self _cmd
    NSInteger paramtersCount = signature.numberOfArguments - 2;
    paramtersCount = MIN(object.count, paramtersCount); 
    
    for (int i = 0; i < paramtersCount; i++) {
        id obj = object[i];
        
        if ([obj isKindOfClass:[NSNull class]]) continue;
        [invocation setArgument:&obj atIndex:i+2];
    }
    
    [invocation invoke];
    
    id returnValue = nil;
    if (signature.methodReturnLength > 0) { //如果有返回值的話,才需要去獲得返回值
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
    
}

    NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"謝華華",@"亞呼呼"];
 SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];
    
    [self performSelector:selector withObject:array];

NSInvocation我是在消息轉發機制中認識的,所以這種方法類似于消息轉發機制中的最后一層,多了創建NSInvocation對象的開銷。而且本質上還是就NSArray進行轉發。

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

推薦閱讀更多精彩內容