iOS延遲的操作有三種:
NSObject的performSelector afterDelay
NSTimer
GCD 的dispatch_after
三種方法都有各自的優缺點:
NSTimer 和 performSelector
缺點:
- 必須保證一個活躍的runloop,子線程中因為不會自動開啟runloop,所以需要手動激活或把它放到MainRunLoop里。
- NSTimer的創建與撤銷必須在同一個線程中,performSelector 的創建和撤銷必須在同一個線程中。
- 內存管理有泄漏風險。
Why?
timer和performSelector 會持有target。當
當一個timer被schedule時候,timer會持有target,NSRunLoop會持有timer,當invalidate被調用,NSRunLoop會釋放timer的持有,timer會釋放對targert的持有,其它沒有方法可以釋放timer對target持有。所以解決內存問題必須手動釋放timer。
注:但是如果我們自己寫一個業務邏輯里面有timer,我們希望內部邏輯具有自完備性,即不需要外部手動調用invalidate,邏輯上說外部也不需要知道這個業務是否使用了timer。
dispatch_after
缺點:
一旦執行,不能撤銷。而performSelector可以用
canclePreviousPerformRequestWithTarget撤銷,timer可以用invalidate撤銷
解決方案
為了解決以上的問題我們可以用GCD實現一個timer,系統會幫我們處理線程邏輯優化線程,而且不需要關心runloop問題,且調用對象不回被強行持有,只需要注意block中的循環引用就可。
具體的代碼如下:
//1.取到queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.創建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.設置timer首次執行時間,間隔,精確度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//執行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的三個缺點,但是
- GCD timer的API太多 。
- 沒有repeats,如果只想執行一次自己得寫標志位控制。
這些我們可以統一封裝成河timer類似的api。
開始封裝:
//設置一個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);
}
外部調用:
//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內執行一次,在主線程,重復執行,直到你cancel到這個timer,如果repeat為No,那么執行一次就會cancel掉這個timer.
- 通過queue我們可以讓timer在不同的線程中執行,queue傳nil默認放入一個子線程中進行。
- 我們可以在dealloc中做cancel,這樣保證了timer運行于對象的整個生命周期
這些都是timer和performSelector不具有的優勢。
外部調用簡單明了,不需要關心timer的釋放問題,和所在的線程是不是開啟了runloop,只要注意一下循環引用,也沒有了需要內存管理的風險。最后來波傳送門GCDTimer