iOS 倒计时 正确姿势

前言

目前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 也是一样的处理方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,862评论 25 708
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,188评论 4 61
  • 父亲在院里编箩筐 母亲在搽着玻璃窗 我在屋后的杨树上 眺望云朵和远方 还有那心中的理想 老鹊在高枝叽唱 啄木鸟啄着...
    叶落岁暮阅读 251评论 0 3
  • 有一种爱叫相遇 万水千山挡不住的崎岖 纵是再没有理由 爱依然有灵犀的旋律 有一种爱叫一见钟情 只是一个对视 爱就如...
    弋夫阅读 140评论 0 0
  • 做事没有主见,总怕做不好怕埋怨,想做不敢做
    857c502c9bdb阅读 306评论 0 0