前言
目前iOS倒計時的業務的使用是相當多,目前關于倒計時的源碼百度一下,遍地都是,但不知道大家有沒有注意到,這些倒計時的代碼很多都是存在bug,而且這個bug基本都是同一個bug。
what ? bug?
是的,真機情況下APP進入后臺之后 GCD 的倒計時 處理停滯狀態,只有在APP喚醒階段才會運行。
bug 栗子
先允許我簡單的舉起一個栗子:
// 倒計時時間
__block NSInteger timeOut = timeLine;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 每秒執行一次
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
// 倒計時結束,關閉
if (timeOut <= 0) {
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
[self setTitle:title forState:UIControlStateNormal];
self.userInteractionEnabled = YES;
});
} else {
NSString *timeStr = [NSString stringWithFormat:@"%0.2d",(int)timeOut];
dispatch_async(dispatch_get_main_queue(), ^{
[self setTitle:[NSString stringWithFormat:@"%@%@", timeStr, subTitle] forState:UIControlStateNormal];
self.userInteractionEnabled = NO;
});
timeOut--;
}
});
dispatch_resume(_timer);
這是一段非常常用的倒計時代碼,GCD 做的。一眼看上去沒啥問題。運行起來當然也沒問題。
但我在真機上測試了,大家看一下效果(大家也可以真機測試一下):
- 第一步:運行倒計時
- 第二部:按Home按鍵,將程序切入后臺,不要殺死
- 第三部:等待3~10s
- 第四部:再次回到APP
如下是運行的截圖
栗子
左下角的紅色 和 綠色的兩個倒計時 在切換后臺之后查了6秒。
大家可以看下結果,真機下,我們的倒計時會延時了。
也就是說,用上述GCD 代碼做的倒計時在iOS真機上,切換到后臺之后,這個CGD 的代碼不執行了,只有在APP處在激活狀態下才能正常使用。
正確栗子
NSDate *oldDate = [NSDate date];
// 倒計時時間
__block NSInteger timeOut = timeLine;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 每秒執行一次
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
// 倒計時結束,關閉
if (timeOut <= 0) {
[self scaleToDefault];
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
[self setTitle:title forState:UIControlStateNormal];
self.userInteractionEnabled = YES;
});
} else {
NSDate *newDate = [NSDate date];
NSTimeInterval timeInterva = [newDate timeIntervalSinceDate:oldDate];
int seconds2 = (timeLine -timeInterva);
NSString *timeStr = [NSString stringWithFormat:@"%0.2d",seconds2];
dispatch_async(dispatch_get_main_queue(), ^{
[self setTitle:[NSString stringWithFormat:@"%@%@", timeStr, subTitle] forState:UIControlStateNormal];
self.userInteractionEnabled = NO;
});
//bug 解決
if (seconds2 <= 1) {
timeOut = 1;
}
timeOut--;
}
});
dispatch_resume(_timer);
具體思路:
- 考慮到在后臺GCD不走,所以我們考慮到NSDate
- 在每次倒計時的情況下,我們走** timeOut的倒計時,我們取兩次的NSDate**
- 每次倒計時時 我們都用當前的NSDate 減去 倒計時最開始的NSDate 秒數的時間差
- 在時間差為 1 時 對 timeOut 進行邏輯處理,最后走出倒計時。
倒計時還有的其它方法,比如通過定時器NSTimer,還可以用NSThread的performSelectorInBackground等等很多方法,如果出現類似上述GCD的bug 可以嘗試用以上思路解決。
當然NSTimer 也是一樣的處理方法