iOS中如何正确释放GCD定时器(dispatch_source_t)

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闪退。

你可能感兴趣的:(iOS中如何正确释放GCD定时器(dispatch_source_t))