偶然間發(fā)現(xiàn)了前人留下的BUG,頁面間倒計(jì)時(shí)在程序進(jìn)入后臺后不刷新损痰,于是又研究了一下倒計(jì)時(shí)相關(guān)的知識福侈,在此做個(gè)匯總記錄。
關(guān)于在后臺運(yùn)行的實(shí)現(xiàn)卢未,有說用播放音樂的方式來做肪凛,感覺太麻煩堰汉,而且審核的時(shí)候也是一個(gè)隱患。
UI相關(guān)的代碼就不放出來了伟墙,
@interface TimerVC ()
{
NSTimeInterval timerTime;
NSTimeInterval displayTime;
NSTimeInterval gcdTime;
}
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, strong) dispatch_source_t gcdTimer;
@property (nonatomic, strong) NSDate *tmpDate; ///< 記錄進(jìn)入后臺的時(shí)間
@end
@implementation TimerVC
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
[self stopGcdTimerAction];
}
- (void)viewDidLoad {
[super viewDidLoad];
timerTime = displayTime = gcdTime = 1000000;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(apperBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(apperForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
}
#pragma mark reload
// 這個(gè)方法在第一篇文章‘監(jiān)聽側(cè)滑返回事件’中有介紹
- (void)didMoveToParentViewController:(UIViewController *)parent {
[super didMoveToParentViewController:parent];
if (!parent) {
/*
NSTimer翘鸭、CADisplayLink都會(huì)強(qiáng)引用self,不會(huì)自動(dòng)釋放戳葵,所以并不會(huì)自動(dòng)走dealloc方法就乓。
*/
[self stopTimerAction];
[self stopDisplayLink];
}
}
#pragma mark -
- (void)timerAction {
if (self.timer) {
return;
}
__weak typeof(&*self) weakSelf = self;
if (@available(iOS 10.0, *)) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf handleTimerAction];
}];
} else {
// Fallback on earlier versions
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimerAction) userInfo:nil repeats:YES];
}
[self.timer fire];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
/*
存在延遲:不管是一次性的還是周期性的timer的實(shí)際觸發(fā)事件的時(shí)間,都會(huì)與所加入的RunLoop和RunLoop Mode有關(guān)拱烁,如果此RunLoop正在執(zhí)行一個(gè)連續(xù)性的運(yùn)算生蚁,timer就會(huì)被延時(shí)觸發(fā).
*/
}
- (void)handleTimerAction {
NSLog(@"timer: %f", timerTime);
timerTime --;
if (timerTime <= 0) {
[self stopTimerAction];
}
}
- (void)stopTimerAction {
if (self.timer) {
[_timer invalidate];
_timer = nil;
NSLog(@"timer release");
}
}
- (void)cadisplayLink {
if (_displayLink) {
return;
}
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink)];
// 間隔多少幀調(diào)用一次,默認(rèn)是1戏自,Apple屏幕刷新率默認(rèn)每秒60次邦投,即每秒調(diào)用60次。
self.displayLink.frameInterval = 60;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
/*
CADisplayLink是一個(gè)能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時(shí)器類擅笔。 CADisplayLink以特定模式注冊到runloop后志衣, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時(shí)候,runloop就會(huì)向 CADisplayLink指定的target發(fā)送一次指定的selector消息剂娄, CADisplayLink類對應(yīng)的selector就會(huì)被調(diào)用一次蠢涝。
iOS設(shè)備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會(huì)在每次刷新結(jié)束都被調(diào)用阅懦,精確度相當(dāng)高和二。使用場合相對專一,適合做UI的不停重繪耳胎,比如自定義動(dòng)畫引擎或者視頻播放的渲染惯吕。不需要在格外關(guān)心屏幕的刷新頻率了,本身就是跟屏幕刷新同步的怕午。
*/
}
- (void)handleDisplayLink {
NSLog(@"display: %f", displayTime);
displayTime --;
if (displayTime <= 0) {
[self displayLink];
}
}
- (void)stopDisplayLink {
if (_displayLink) {
[_displayLink invalidate];
_displayLink = nil;
NSLog(@"display release");
}
}
- (void)apperBackground {
_tmpDate = [NSDate date];
}
- (void)apperForeground {
NSDate *date = [NSDate date];
int second = (int)ceil([date timeIntervalSinceDate:_tmpDate]);
int tmp = gcdTime - second;
if (tmp > 0) {
gcdTime -= second;
}
else {
gcdTime = 0;
}
val = timerTime - second;
if (tmp > 0) {
timerTime -= second;
}
else {
timerTime = 0;
}
tmp = displayTime - second;
if (tmp > 0) {
displayTime -= second;
}
else {
displayTime = 0;
}
}
- (void)gcdTimerAction {
if (self.gcdTimer) {
return;
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/*
dispatch_source_set_timer 當(dāng)我們使用dispatch_time 或者 DISPATCH_TIME_NOW 時(shí)废登,系統(tǒng)會(huì)使用默認(rèn)時(shí)鐘來進(jìn)行計(jì)時(shí)。然而當(dāng)系統(tǒng)休眠的時(shí)候郁惜,默認(rèn)時(shí)鐘是不走的堡距,也就會(huì)導(dǎo)致計(jì)時(shí)器停止。使用 dispatch_walltime 可以讓計(jì)時(shí)器按照真實(shí)時(shí)間間隔進(jìn)行計(jì)時(shí)兆蕉。
但是設(shè)置為dispatch_walltime(NULL, 0)之后羽戒,如果在設(shè)置里設(shè)置日期為之前的日期,則不會(huì)再調(diào)用次方法虎韵,而設(shè)置為DISPATCH_TIME_NOW則可以
*/
dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
__weak typeof(&*self) weakSelf = self;
dispatch_source_set_event_handler(_gcdTimer, ^{
[weakSelf handleGcdTimerAction];
});
dispatch_resume(_gcdTimer);
/*
dispatch_suspend(<#dispatch_object_t _Nonnull object#>)
這個(gè)是掛起易稠,不能再這之后釋放_gcdTimer,即_gcdTimer = nil;會(huì)崩潰包蓝,釋放只能在dispatch_source_cancel()之后驶社。
*/
}
- (void)handleGcdTimerAction {
NSLog(@"gcd: %f", gcdTime);
gcdTime --;
if (gcdTime <= 0) {
[self stopGcdTimerAction];
}
}
- (void)stopGcdTimerAction {
if (_gcdTimer) {
dispatch_source_cancel(_gcdTimer);
_gcdTimer = nil;
NSLog(@"gcd release");
}
}
@end
加油F罅俊!亡电!