zoukankan      html  css  js  c++  java
  • cell的复用机制导致的倒计时问题解决

      最近项目中用到了tableView的多个cell倒计时系统问题,本觉得很简单的一个事,一做发现还没这么简单,就此记录。

      下面方法模拟网络请求返回数据。

      按照常规思路,根据网络请求返回remainTime,封装模型,存到数组中,再在表格代理方法中赋值给cell

      cell中根据传入模型中的remainTime属性,开启定时器每隔1s调用如下方法

      程序一运行发现问题:每当表格滚动时,表格代理方法cellForRowAtIndexPath会不断重复调用,从数组中取得模型赋值给cell,而模型中的remainTime是固定的,于是倒计时系统不断重复开始倒计时。

    ​  发现问题点,开始着手解决。开始想到的是方法是在控制器中也开启一套定时器系统,当服务器数据remainTime返回时,将其中remainTime大于0s的数据保存在一个字典中,对所有键值对开始倒计时。

      下面方法模拟网络数据返回,对所有remainTime大于0的字段保存到字典self.timerDic中

      控制器中,定时器每隔1s调用方法,对remainTime减一后再覆盖掉原本键值对
     
     
     
      于是,字典self.timerDi中所有键值对的value每隔1s递减1,直到最后value都变为0。在cellForRowAtIndexPath方法中插入如下
     
     

      cell属性model中的remainTime字段从这个一直变化的self.timerDic字典中取值,于是滚动视图时cell获取到的就不是一个固定的remainTime,效果如下

    ​​

    ​  此时已经解决了表格滚动时倒计时重复计时问题,但可以看到多次滚动后会造成如上显示错误,这是由于控制器和cell两套定时系统时差而引起的,具体后面分析。

        此路貌似不通,于是我想到了KVO,让cell监听控制器中remainTime的数值变化​

     这方法还真走效,在cell的observeValueForKeyPath中确实能监听到remainTime的数值变化,数值也异常正确,但同时一个重大问题也产生了,由于cell的复用,cell上所显示的倒计时系统相互错乱,试了几种方法都无法解决,放弃。
     

      仔细分析上面倒计时时差原因,发现时差产生是由于定时器调用频率导致。举个场景说明:控制器返回数据时remainTime是10,过了0.9999s后用户滚动表格,此时cell从字典self.timerDic中取到的remainTime仍旧是10,于是cell定时系统的remainTime值比控制器的慢了0.9999s。同理分析也可能快0.9999s,于是可能会引发最多2s的极限误差。

      找到具体原因修改就比较容易了,使用CADisplayLink,一分钟调用60次countDown方法,每次减去1/60s,则最大误差只有2*1/60s,比较准确,能够满足要求

      最后做下适当优化:定时器在主线程工作,调用频率很高,每次调用还要遍历字典对每一个value递减后覆盖旧值,故希望定时器能在后台工作。定时器工作在后台线程时会自动将其注册到后台线程的runloop,而runloop依托线程但并不会自动创建,此时countDown无法接收到事件回调,需要手动生成runloop并保证其不会退出。这里参照AFN中的生成方法,核心代码如下:

     
      刚开始学swift,因工作还在用OC,只能平时练练手了,github上代码为swift版
      github地址:https://github.com/zhangmaliang/CountDown
     
     
    发现上面方法有误。实测更正如下:
    tableViewController:

    @implementation ViewController{

        NSMutableArray *_arr;

        NSTimer *_timer;

        NSInteger _notifNum;

    }

     - (void)viewDidLoad {

        [super viewDidLoad];

          _arr = @[].mutableCopy;

         [self loadNewData];

    }

     // 下拉刷新

    - (void)loadNewData{

        [_arr removeAllObjects];

        for (int i = 0; i < 100; i++) {

            [_arr addObject:@(10 * i)];

        }

        [self.tableView reloadData];

        // 清空

        _notifNum = 0;

        [self startTimer];

    }

    // 上拉刷新

    - (void)loadMoreData{

        for (int i = 0; i < 100; i++) {

            NSInteger num = 10 * i;  // 服务器拿到数字

            num += _notifNum;        // 将数据加上当前计时器的数字

            [_arr addObject:@(num)];

        }

        [self.tableView reloadData];

    }

    - (void)startTimer{

        if (_timer) return;

        _timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {

            [[NSNotificationCenter defaultCenter] postNotificationName:@"NSNotification" object:@(_notifNum)];

            _notifNum++;

        }];

        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

        return _arr.count;

    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

        TestTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"1111"];

        if (!cell) {

            cell = [[TestTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"1111"];

        }

        cell.startTime = [_arr[indexPath.row] integerValue];

        return cell;

    }

     cell:

    @implementation TestTableViewCell{

        NSInteger _num;

    }

    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{

        if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {

            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(haha:) name:@"NSNotification" object:nil];

        }

        return self;

    }

    - (void)haha:(NSNotification *)noti{

        _num = [noti.object integerValue];

        self.startTime = _startTime;

    }

    - (void)setStartTime:(NSInteger)startTime{

        _startTime = startTime;

        if (_startTime - _num > 0) {

            self.textLabel.text = @(_startTime - _num).description;

        }else{

            self.textLabel.text = @"停止";

        }

    }

  • 相关阅读:
    带宽利用率提升50%,腾讯云联网架构方案解析
    重构实践:基于腾讯云Elasticsearch搭建QQ邮箱全文检索
    存算分离下写性能提升10倍以上,EMR Spark引擎是如何做到的?
    秒级去重:ClickHouse在腾讯海量游戏营销活动分析中的应用
    降本提效,贝壳搜索推荐架构统一之路
    亿级用户,腾讯看点信息流推荐系统的架构挑战
    优秀程序员,如何提高架构能力?
    如何创建体元栅格?
    导出属性表字段结构
    去除镶嵌数据集中影像的黑边或白边
  • 原文地址:https://www.cnblogs.com/zhangmaliang/p/5102518.html
Copyright © 2011-2022 走看看