iOS大量数据倒计时及可见的数据才轮询

![WX20170810-151337.png](http://upload-images.jianshu.io/upload_images/1605558-90e68cb0012df555.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

前言:
在开发中遇到这么一个需求,一个列表页展示很多商品数据,这些商品数据的信息是在实时变动的,可以简单的说是一个TableView的Cell里每个Cell是一个商品,每个商品的数据通过接口轮训数据并
实时展示显示的数据,同时还要保证离开这个页面后停止无意义的数据变换及轮询,减少不必要的服务器带宽
知识点:Cell重用、定时器、通知、网络请求、倒计时、轮询可见部分
过程:
查找了很多资料,在参考了 iOS 在cell中使用倒计时的处理方法这篇博文后找了思路
1、由于Cell太多,不能每个Cell都创建一个Timer刷定时器,而且也不好控制让离开改某个View时停止定时器,在进入该页面后又重新开始定时器,所以需要用到的页面,在对应的Controller里使用一个定时器统一管理

// .h
@interface LHCountDownManager : NSObject
// 倒计时通知名字
@property (nonatomic,copy) NSString  *countDownNotifyKey;
/// 时间差(单位:秒)
@property (nonatomic, assign) NSInteger timeInterval;

/// 开始倒计时
- (void)start;
/// 刷新倒计时
- (void)reload;
/// 停止倒计时
- (void)invalidate;

+ (instancetype)managerWithCountDownKey:(NSString *)countDownKey;
@end

// .m
#import "LHCountDownManager.h"
@interface LHCountDownManager()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LHCountDownManager
+ (instancetype)managerWithCountDownKey:(NSString *)countDownKey
{
    LHCountDownManager *manager = [LHCountDownManager new];
    manager.countDownNotifyKey = countDownKey;
    return manager;
}
- (void)start {
    // 启动定时器
    [self timer];
}

- (void)reload {
    // 刷新只要让时间差为0即可
    _timeInterval = 0;
}

- (void)invalidate {
    [self.timer invalidate];
    self.timer = nil;
    self.timeInterval = 0;
}

- (void)timerAction {
    // 时间差+1
    self.timeInterval ++;
    
    // 发出通知--可以将时间差传递出去,或者直接通知类属性取
    [[NSNotificationCenter defaultCenter] postNotificationName:self.countDownNotifyKey object:nil userInfo:@{@"TimeInterval" : @(self.timeInterval)}];
    // 对最大值进行限制
    if (self.timeInterval >= NSUIntegerMax) {
        self.timeInterval = 0;
    }
    // 对最小值进行限制
    if (self.timeInterval <= 0) {
        self.timeInterval = 0;
    }
}
- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    return _timer;
}
@end

2、由统一管理定时器的类进行按一定时间倒计时发通知,然后由有需要变动的Cell进行接收通知并处理,如果Cell接收到的通知后进行对Cell管理的model的值如果有变化,进行倒计时显示

#pragma mark - ******** 倒计时通知处理
- (void)setCountDownNotifyName:(NSString *)countDownNotifyName
{
    // 如果以前设置的倒计时通知有变化,去除以前的通知
    if (_countDownNotifyName != nil && ![_countDownNotifyName isEqualToString:countDownNotifyName]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:_countDownNotifyName object:nil];
    }
    // 如果现在设置的通知与之前的不一致,添加通知
    if (countDownNotifyName !=nil && ![_countDownNotifyName isEqualToString:countDownNotifyName]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(countDownChange:) name:countDownNotifyName object:nil];
    }
    _countDownNotifyName = countDownNotifyName;
}
#pragma mark 显示倒计时减一
- (void)countDownChange:(NSNotification *)notify
{
    // 倒计时减一
    self.model.goodsTime --;
    NSInteger countDown = self.model.goodsTime;
    /// 当倒计时到了进行回调
    if (countDown <= 0) {
        countDown = 0;
    }
    NSString *cuountDownText = [NSString stringWithFormat:@"%02zd:%02zd:%02zd", countDown/3600, (countDown/60)%60, countDown%60];
    self.timeLbl.text = cuountDownText;
}
#pragma mark - ******** 轮询价格变化处理
- (void)setPriceChangeNotifyName:(NSString *)priceChangeNotifyName
{
    // 如果以前设置的倒计时通知有变化,去除以前的通知
    if (_priceChangeNotifyName != nil && ![_priceChangeNotifyName isEqualToString:priceChangeNotifyName]) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:_priceChangeNotifyName object:nil];
    }
    // 如果现在设置的通知与之前的不一致,添加通知
    if (priceChangeNotifyName !=nil && ![_priceChangeNotifyName isEqualToString:priceChangeNotifyName]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(priceChange:) name:priceChangeNotifyName object:nil];
    }
    _priceChangeNotifyName = priceChangeNotifyName;
}
#pragma mark 内容经过轮询后显示最新内容
- (void)priceChange:(NSNotification *)notify
{
    NSDictionary *userInfo = notify.userInfo;
    NSString *goodsId = userInfo[@"goodsId"];
    // goodsId等于空或不是当前Cell显示的model,直接不处理
    if (goodsId == nil || [goodsId isEqualToString:@""] || ![goodsId isEqualToString:self.model.goodsId]) {
        return;
    }
    
    NSString *currentNamePrice = [NSString stringWithFormat:@"%@出价¥%.2f",self.model.goodsBuyer,self.model.goodsPrice];
    // 如果购买者或者购买的价格没有变化不处理
    if (currentNamePrice == nil || [self.oldNamePrice isEqualToString:currentNamePrice]) {
        return;
    }
    // 更新数据
    self.peoplePriceLbl.text = currentNamePrice;
    NSInteger countDown = self.model.goodsTime;
    /// 当倒计时到了进行回调
    if (countDown <= 0) {
        countDown = 0;
        
    }
    NSString *cuountDownText = [NSString stringWithFormat:@"%02zd:%02zd:%02zd", countDown/3600, (countDown/60)%60, countDown%60];
    self.timeLbl.text = cuountDownText;
    self.oldNamePrice = currentNamePrice;
    
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - ******** 设置数据
- (void)setModel:(LHProductModel *)model
{
    _model = model;
    [self.productImg sd_setImageWithURL:[NSURL URLWithString:model.goodsImage] placeholderImage:[UIImage imageNamed:@"nahan"]];
    self.nameLbl.text = model.goodsName;
    NSString *currentNamePrice = [NSString stringWithFormat:@"%@出价¥%.2f",self.model.goodsBuyer,self.model.goodsPrice];
    self.peoplePriceLbl.text = currentNamePrice;
    self.oldNamePrice = currentNamePrice;
    NSInteger countDown = model.goodsTime;
    /// 当倒计时到了进行回调
    if (countDown <= 0) {
        countDown = 0;
        
    }
    NSString *cuountDownText = [NSString stringWithFormat:@"%02zd:%02zd:%02zd", countDown/3600, (countDown/60)%60, countDown%60];
    self.timeLbl.text = cuountDownText;
}

3、如何轮询只显示的Cell的数据,我的办法是轮询也统一放在倒计时管理里处理,然后再Cell的 _setModel里把旧的model移除需要轮询的队列,把新要显示的model加入要轮询的队列,然后管理定时器的类会定时轮询这些model,这样一个定时器管理类就能把倒计时显示和最少量轮询同时完成
3.1 、管理一个保持轮询中的数组pollingListM
3.2、 在每次重新请求了数据后将pollingListM清空
3.3、在cellForRowAtIndexPath方法里将被替换不显示的model移除需要轮询的数组,将接近显示的model放入轮询数组,然后通过定时器在列表里轮询列表数组

#import "LHHomeViewController.h"
#import "LHProductTableViewCell.h"
#import "ProductService.h"
#import "LHCountDownManager.h"
#import "LHGoodsDetailController.h"

#define kHomeCountDownTimerNotifiyKey @"kHomeCountDownTimerNotifiyKey"
#define kHomeProductChangeNotifiyKey @"kHomeProductChnageNotifiyKey"

@interface LHHomeViewController () 
@property (nonatomic,strong) UITableView * tableView;
@property (nonatomic,strong) ProductService * service;
@property (nonatomic,strong) LHCountDownManager * countDownManager;
@property (nonatomic,strong) NSMutableArray * pollingListM;
@end

@implementation LHHomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self _loadFirstPage];
    [self _addNotifications];
}
- (void)_loadFirstPage
{
    __weak typeof(self) weakSelf = self;
    [self.service loadFirstPageProductListWithBlock:^(BOOL success, NSString *msg, BOOL hasMore) {
        if (success) {
            [weakSelf.pollingListM removeAllObjects];
            // 有重新加载了数据就要将轮询数组里的数据全清空一遍
            [weakSelf.tableView reloadData];
        }
    }];
}
- (void)_addNotifications
{
    // 添加定时器变化
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(countDownChange:)
                                                 name:kHomeCountDownTimerNotifiyKey
                                               object:nil];
}
#pragma mark - ******** 轮询所有保存在轮询数组里的内容
- (void)countDownChange:(NSNotification *)notify
{
    // 2秒轮询一次
    if (self.countDownManager.timeInterval % 2 == 0) {
        NSString *ids = [[self.pollingListM valueForKeyPath:@"goodsId"] componentsJoinedByString:@","];
        NSLog(@"变量的数组:%@",ids);
        // 对所有轮询数组里的数据进行轮询请求
        for (LHProductModel *model in self.pollingListM) {
            // 这个方法只改变model的值,不重新创建model
            [self.service pollingModelWithModel:model block:^(BOOL success, id msg) {
                // 如果请求成功,发送通知修改显示内容
                if (success && [msg isKindOfClass:[LHProductModel class]]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kHomeProductChangeNotifiyKey object:nil userInfo:@{@"goodsId":((LHProductModel *)msg).goodsId?:@""}];
                }
            }];
        }
    }
}
#pragma mark - ******** 进入页面或离开页面开启关闭定时器
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.countDownManager start];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.countDownManager invalidate];
}
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
#pragma mark - ******** UITableViewDataSource && UITabelViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger count = self.service.dataList.count;
    return count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 130;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    LHProductModel *model = self.service.dataList[indexPath.row];
    LHProductTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"LHProductTableViewCell"];
    // 将重用Cell之前保存的model从轮询数组里去除
    if (cell.model != nil) {
        [self.pollingListM removeObject:cell.model];
    }
    // 将接近要显示的Cell保存的model放到轮询数组里用于轮询
    if (model != nil) {
        [self.pollingListM addObject:model];
    }
    // 设置让Cell可以处理倒计时的通知
    cell.countDownNotifyName = kHomeCountDownTimerNotifiyKey;
    cell.priceChangeNotifyName = kHomeProductChangeNotifiyKey;
    // 将数据赋值给Cell显示
    cell.model = model;
    return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    });
    LHProductModel *model = self.service.dataList[indexPath.row];
    LHGoodsDetailController *vc = [LHGoodsDetailController new];
    vc.model = model;
    vc.hidesBottomBarWhenPushed = YES;
    [self.navigationController pushViewController:vc animated:YES];
}
#pragma mark - ******** getter && setter
- (UITableView *)tableView
{
    if (_tableView == nil) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.dataSource = self;
        _tableView.delegate = self;
        [_tableView registerNib:[UINib nibWithNibName:@"LHProductTableViewCell" bundle:nil] forCellReuseIdentifier:@"LHProductTableViewCell"];
    }
    return _tableView;
}
- (ProductService *)service
{
    if (_service == nil) {
        _service = [ProductService new];
    }
    return _service;
}
- (LHCountDownManager *)countDownManager
{
    if (_countDownManager == nil) {
        _countDownManager = [LHCountDownManager managerWithCountDownKey:kHomeCountDownTimerNotifiyKey];
    }
    return _countDownManager;
}
- (NSMutableArray *)pollingListM
{
    if (_pollingListM == nil) {
        _pollingListM = [NSMutableArray array];
    }
    return _pollingListM;
}
@end

具体项目demo

你可能感兴趣的:(iOS大量数据倒计时及可见的数据才轮询)