iOS延遲的操作有三種:
NSObject的performSelector afterDelay
NSTimer
GCD 的dispatch_after
三種方法都有各自的優(yōu)缺點(diǎn):
NSTimer 和 performSelector
缺點(diǎn):
- 必須保證一個活躍的runloop,子線程中因?yàn)椴粫詣娱_啟runloop,所以需要手動激活或把它放到MainRunLoop里。
- NSTimer的創(chuàng)建與撤銷必須在同一個線程中,performSelector 的創(chuàng)建和撤銷必須在同一個線程中。
- 內(nèi)存管理有泄漏風(fēng)險。
Why?
timer和performSelector 會持有target。當(dāng)
當(dāng)一個timer被schedule時候,timer會持有target,NSRunLoop會持有timer,當(dāng)invalidate被調(diào)用,NSRunLoop會釋放timer的持有,timer會釋放對targert的持有,其它沒有方法可以釋放timer對target持有。所以解決內(nèi)存問題必須手動釋放timer。
注:但是如果我們自己寫一個業(yè)務(wù)邏輯里面有timer,我們希望內(nèi)部邏輯具有自完備性,即不需要外部手動調(diào)用invalidate,邏輯上說外部也不需要知道這個業(yè)務(wù)是否使用了timer。
dispatch_after
缺點(diǎn):
一旦執(zhí)行,不能撤銷。而performSelector可以用
canclePreviousPerformRequestWithTarget撤銷,timer可以用invalidate撤銷
解決方案
為了解決以上的問題我們可以用GCD實(shí)現(xiàn)一個timer,系統(tǒng)會幫我們處理線程邏輯優(yōu)化線程,而且不需要關(guān)心runloop問題,且調(diào)用對象不回被強(qiáng)行持有,只需要注意block中的循環(huán)引用就可。
具體的代碼如下:
//1.取到queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.創(chuàng)建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.設(shè)置timer首次執(zhí)行時間,間隔,精確度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//執(zhí)行timer
__weak __typeof(self)weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
[weakSelf doSomeThing];
});
//4.激活timer
dispatch_resume(timer);
//5.取消timer
dispatch_source_cancel(timer);
用這個方式可以解決timer的三個缺點(diǎn),但是
- GCD timer的API太多 。
- 沒有repeats,如果只想執(zhí)行一次自己得寫標(biāo)志位控制。
這些我們可以統(tǒng)一封裝成河timer類似的api。
開始封裝:
//設(shè)置一個timerContainer容器捕獲所有timer,key:timerName ,value:timer
+ (instancetype)scheduledDispatchTimerWithName:(NSString *)timerName
timeInterval:(NSTimeInterval)interval
queue:(dispatch_queue_t)queue
repeats:(BOOL)repeats
action:(dispatch_block_t)action {
NSParameterAssert(timerName);
GCDTimer *gcdTimer = [[GCDTimer alloc] init];
if (!queue) {
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
dispatch_source_t timer = [gcdTimer.timerContainer objectForKey:timerName];
if (!timer) {
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
[gcdTimer.timerContainer setObject:timer forKey:timerName];
dispatch_resume(timer);
}
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
__weak GCDTimer *weakTimer = gcdTimer;
dispatch_source_set_event_handler(timer, ^{
action();
if (!repeats) {
[weakTimer cancelTimerWithName:timerName];
}
});
return gcdTimer;
}
- (void)cancelTimerWithName:(NSString *)timerName {
dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
if (!timer) {
return ;
}
[self.timerContainer removeObjectForKey:timerName];
dispatch_source_cancel(timer);
}
外部調(diào)用:
//1.外部開始timer
GCDTimer *gcdtimer = [GCDTimer scheduledDispatchTimerWithName:@"myTime"
timeInterval:2
queue:dispatch_get_main_queue()
repeats:YES
action:^{
NSLog(@"GCDTimer");
}];
//2.外部cancelTimer
[gcdtimer cancelTimerWithName@"myTime"];
上面我們弄了個timer,2s內(nèi)執(zhí)行一次,在主線程,重復(fù)執(zhí)行,直到你cancel到這個timer,如果repeat為No,那么執(zhí)行一次就會cancel掉這個timer.
- 通過queue我們可以讓timer在不同的線程中執(zhí)行,queue傳nil默認(rèn)放入一個子線程中進(jìn)行。
- 我們可以在dealloc中做cancel,這樣保證了timer運(yùn)行于對象的整個生命周期
這些都是timer和performSelector不具有的優(yōu)勢。
外部調(diào)用簡單明了,不需要關(guān)心timer的釋放問題,和所在的線程是不是開啟了runloop,只要注意一下循環(huán)引用,也沒有了需要內(nèi)存管理的風(fēng)險。最后來波傳送門GCDTimer