iOS中如何正确释放GCD定时器(dispatch_source_t)
一.现象
通过云迹的崩溃,查询到崩溃在福袋的释放缓存的方法(clearLuckyBagInfo)中,然后就查看这个释放缓存的方法,发现这个方法中都是简单的释放,也有保护操作,就很纳闷儿为什么崩溃在了这里.
- (void)clearLuckyBagInfo{
self.LuckyBagDetailModel = nil;
self.contentView = nil;
self.residueTime = 0;
if (self.countDownTimer) {
dispatch_cancel(self.countDownTimer);
self.countDownTimer = nil;
}
self.isSuspendTimer = NO;
self.liveDto = nil;
[self.winPriceArray removeAllObjects];
self.winPriceArray = nil;
if (self.tenSecondTimer) {
dispatch_cancel(self.tenSecondTimer);
self.tenSecondTimer = nil;
}
self.IsReciveLotteryMessage = NO;
self.timeGone = 0;
}
二.查询和验证
查看了上面的释放方法中,通过分析只有可能是定时器释放时引起的crash,然后就去查询dispatch_source_t的crash问题,结果发现了在dispatch_suspend 状态下,不能去释放定时器,如果此时释放定时器,就会crash.
20210901143117503.png然后查看代码中为了定时器的时间退到后台再回来时的准确性,就在退到后台中将定时器挂起(dispatch_suspend ),回到前台是发现福袋未结束时就重新开启定时器(dispatch_resume),如果用户从后台回到前台时福袋已经结束了,这时就没调用dispatch_resume开启定时器,这时候释放定时器就会引起crash.然后就和测试验证了这个场景,确实会引起crash.
// 程序进入后台
- (void)resignActive:(NSNotification *)notification {
if ([self.LuckyBagDetailModel.drawStatus isEqualToString:@"0"] && self.countDownTimer) {
[self pauseCountDownTimer];//挂起定时器
NSDate* currentdate = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[currentdate timeIntervalSince1970];
[[NSUserDefaults standardUserDefaults] setObject:@(a) forKey:snliveLuckyBagResignActiveDate];
}
}
// 程序进入前台
- (void)becomeActive:(NSNotification *)notification {
if ([self.LuckyBagDetailModel.drawStatus isEqualToString:@"0"] && self.countDownTimer) {
[self resumeCountDownTimer];//重启定时器
NSDate* currentdate = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[currentdate timeIntervalSince1970];
NSTimeInterval resignDate = [[NSUserDefaults standardUserDefaults] doubleForKey:snliveLuckyBagResignActiveDate];
self.timeGone = a - resignDate;
}else{
self.timeGone = 0;
}
}
三.解决办法
1.设置属性 isSuspendTimer 记录定时器timer是否 处于dispatch_suspend的状态。在定时器挂起时设置为YES,重新启动时设置为NO. 只要在 调用dealloc 时判断下,已经调用过 dispatch_suspend 则再调用下 dispatch_resume后再cancel,然后再释放timer。就不会引起崩溃了.
//计时器是否挂起
@property (nonatomic, assign) BOOL isSuspendTimer;
- (void)pauseCountDownTimer{//挂起
if(self.countDownTimer){
self.isSuspendTimer = YES;
dispatch_suspend(_countDownTimer);
}
}
- (void)resumeCountDownTimer{//恢复
if(self.countDownTimer){
self.isSuspendTimer = NO;
dispatch_resume(_countDownTimer);
}
}
- (void)clearLuckyBagInfo{
self.LuckyBagDetailModel = nil;
self.contentView = nil;
self.residueTime = 0;
//释放定时器是先判断该定时器是否被挂起
if (self.countDownTimer) {
if (self.isSuspendTimer) {
dispatch_resume(self.countDownTimer);
}
dispatch_cancel(self.countDownTimer);
self.countDownTimer = nil;
}
self.isSuspendTimer = NO;
self.liveDto = nil;
[self.winPriceArray removeAllObjects];
self.winPriceArray = nil;
if (self.tenSecondTimer) {
dispatch_cancel(self.tenSecondTimer);
self.tenSecondTimer = nil;
}
self.IsReciveLotteryMessage = NO;
self.timeGone = 0;
}
四.总结(dispatch_source_t)
dispatch_suspend 状态下直接释放当前控制器或者释放定时器,会导致定时器崩溃。并且初始状态(未调用dispatch_resume)、挂起状态,都不能直接调用dispatch_source_cancel(timer),调用就会导致app闪退。
网友评论