首先來(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í)候所在的線程是一致的
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中,才可。

