iOS performSelector && afterDelay 與線程之間的關(guān)系

首先來(lái)段代碼

 - (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self performSelector:@selector(test:) withObject:@"aaa"];
        [self performSelector:@selector(test:) withObject:@"bbb" afterDelay:3];
    });
}

- (void)test:(NSString *)object
{
    NSLog(@"======>:%@",object);
}

上面代碼會(huì)輸出什么呢?來(lái)跑一下看看:

======>:aaa

只會(huì)輸出aaa,說(shuō)明 [self performSelector:@selector(test:) withObject:@"bbb" afterDelay:3];并沒(méi)有在延遲3s后執(zhí)行test方法。下面看下為什么會(huì)這樣。

performSelector && performSelector:withObject:afterDelay:區(qū)別

  • performSelector
    我們看下performSelector的定義:

    截屏2020-04-18下午3.26.07.png

    看到performSelector方法是在NSObject類(lèi)中定義的,接下來(lái)再看看加上afterDelay的performSelector方法定義

  • performSelector:withObject:afterDelay:


    截屏2020-04-18下午3.27.09.png

    可以看到是在NSRunloop中定義的,下面說(shuō)一下具體的區(qū)別

  • 區(qū)別:
    1.performSelector
    實(shí)際是為了方便使用objc_msgSend 的一個(gè)簡(jiǎn)單的封裝,等同于:

((void (*) (id, SEL, NSString *)) objc_msgSend)(self, NSSelectorFromString(@"test:"), @"aaa");

performSelector默認(rèn)最多只可傳遞兩個(gè)參數(shù),若需多參參考以下方式:
一種是使用NSInvocation,利用了runtime的反射機(jī)制,效率較低,可讀性不高;
第二種是將參數(shù)封裝進(jìn)NSArray、NSDictionary等對(duì)象,可讀性強(qiáng),效率高;
第三種是使用objc_msgSend重寫(xiě)performSelector。

而且他的方法執(zhí)行所在的線程跟調(diào)用他的時(shí)候所在的線程是一致的

  1. performSelector:withObject:afterDelay:

拿上面的代碼

 [self performSelector:@selector(test:) withObject:@"bbb" afterDelay:3];

該方法將延遲3秒后再執(zhí)行test方法。說(shuō)到對(duì)時(shí)間方面的處理在項(xiàng)目中經(jīng)常用到的是NSTimer:當(dāng)一個(gè)NSTimer注冊(cè)到Runloop后,Runloop會(huì)重復(fù)的在相應(yīng)的時(shí)間點(diǎn)注冊(cè)事件,當(dāng)然Runloop為了節(jié)省資源并不會(huì)在準(zhǔn)確的時(shí)間點(diǎn)觸發(fā)事件。
performSelector:withObject:afterDelay:其實(shí)就是在內(nèi)部創(chuàng)建了一個(gè)NSTimer,然后會(huì)添加到當(dāng)前線程的Runloop中,所以當(dāng)該方法添加到子線程中的時(shí)候需要注意一點(diǎn):子線程中的runloop默認(rèn)是沒(méi)有啟動(dòng)的狀態(tài)。所以我們要讓當(dāng)前子線程的runloop跑起來(lái):

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSRunLoop currentRunLoop] run];
        [self performSelector:@selector(test:) withObject:@"bbb" afterDelay:3];
    });

運(yùn)行一下發(fā)現(xiàn)并沒(méi)有任何作用,是因?yàn)閞un方法只是嘗試想要開(kāi)啟當(dāng)前線程中的runloop,但是如果該線程中并沒(méi)有任何事件(source、timer、observer)的話,并不會(huì)成功的開(kāi)啟。
所以代碼要改一下:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self performSelector:@selector(test:) withObject:@"bbb" afterDelay:3];
        [[NSRunLoop currentRunLoop] run];
    });

打印一下,會(huì)發(fā)現(xiàn)3s后會(huì)正常輸出:======>:bbb
需要注意的是:
對(duì)于該performSelector延遲方法而言,如果在主線程中調(diào)用,那么test方法也是在主線程中執(zhí)行;如果是在子線程中調(diào)用,那么test也會(huì)在該子線程中執(zhí)行。


  • 結(jié)論:
    1.performSelector:withObject:只是一個(gè)單純的消息發(fā)送,就是對(duì) objc_msgSend的封裝,和時(shí)間沒(méi)有一點(diǎn)關(guān)系,所以不需要添加到子線程的Runloop中也能執(zhí)行。
    2.performSelector:withObject:afterDelay:是在內(nèi)部創(chuàng)建了一個(gè)NSTimer,然后會(huì)添加到當(dāng)前線程的Runloop中,如果添加在子線程,由于子線程中的runloop默認(rèn)是沒(méi)有啟動(dòng)的狀態(tài),所以我們需要把子線程的Runloop跑起來(lái)才可。而且還要注意調(diào)用的順序,必須先把timer (performSelector:withObject:afterDelay:)注冊(cè)到runloop中,才可。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容