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 = @"停止";

    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM