倒计时工具类:PYContDownManager

倒计时.gif
  1. 左边是输出台,右边是tableView,点击后modal了一个控制器,停止了计时器

一、主要功能

对于tableViewCell中,总会碰见有多个cell随机计时的问题,于是写了一个工具类。
里面封装了停止倒计时和开始倒计时。提供了倒计时的单位计时时间,以及距离当前时间还剩多长时间开始及时的变量,使用方便,异步线程计算。性能一般。


二、头文件.h

1. 创建方法

1.用着两个方法进行创建,需要的外部参数已经注明,内部对这些参数进行了储存

/**
 * 请用这个方法(或者对应的init方法)创建对象
 * @parma countDownStartTime 剩多长时间开始倒计时
 * @parma countDownUnit 倒计时单位时间
 * @parma modelArray 需要倒计时的model数组
 * @parma modelDateKey model储存到期时间的属性名
 * @parma modelCountDownKey model储存剩余时间的属性名
 * @parma modelDateType model中储存的到期时间是否为剩余时间
 */
-(instancetype)initWithCountDownStartTime: (long)countDownStartTime andCountDownUnit: (double)countDownUnit andModelArray: (NSArray *)modelArray andModelDateKey: (NSString *)modelDateKey andModelCountDownKey: (NSString *)modelCountDownKey andModelDateType: (PYContDownManagerModelDateType)modelDateType;
/**
 * 请用这个方法(或者对应的init方法)创建对象
 * @parma countDownStartTime 剩多长时间开始倒计时
 * @parma countDownUnit 倒计时单位时间
 * @parma modelArray 需要倒计时的model数组
 * @parma modelDateKey model储存到期时间的属性名
 * @parma modelCountDownKey model储存剩余时间的属性名
 * @parma modelDateType model中储存的到期时间是否为剩余时间
 */
+(instancetype)countDownManagerWithCountDownStartTime: (long)countDownStartTime andCountDownUnit: (double)countDownUnit andModelArray: (NSArray *)modelArray andModelDateKey: (NSString *)modelDateKey andModelCountDownKey: (NSString *)modelCountDownKey andModelDateType: (PYContDownManagerModelDateType)modelDateType;

2. 常用的方法:

  1. 这个方法是用于外界进行UI界面刷新的。在这里面可以进行model数组的遍历,找出小于0的model,在做相关UI操作(默认回到了主线程)
/**
 *没次单位时间过后就会掉一次,可以在这里刷新UI(已经回到了主线程)
 *@param countdownDataFredbackWithBlock 给外界提供了model (这时候model已经赋值成功了),
 */
-(void)countdownDataFredbackWithBlock: (void(^)(id model,long long CountDown))countdownDataFredbackWithBlock;
  1. 取消定时器:在大多数情况下,点击cell后会跳转到相应的二级界面,那么我们就有必要把定时器关掉
/**
 *取消定时器
 */
-(void)cancelTimer;
  1. 定时器的重启: 同样,在回调到当前界面的时候,定时器应该是重新开启的(其实内部是重新创建了一个定时器)
/**
 *开启定时器
 */
-(void)resumeTimer;

3.常用的属性

1.很多情况下,服务器请求回来的时间与本地时间并不一致,这样会导致倒计时的不准确,所以我们要把本地参考的当前时间与服务器的当前时间作统一,把服务器时间传入这里,就可以来更正倒计时的准确度
如果对服务器与客户端时间矫正问题有疑问请看这里

/**
 * 客户端时间,默认为手机的当前时间。如果有偏差可以在这里调整
 */
@property (nonatomic,strong) NSDate *clientTime;

三、具体实现的逻辑.m

1. 逻辑

  1. 类的内部创建了一个定时器。每次定时器执行后都会遍历model数组,并且判断是否应该倒计时,如果需要倒计时,那么就通过kvc给model的剩余时间属性赋值。
  1. 每隔一个单位时间间隔,就会执行回调方法的block
  2. 根据对model数组的遍历生成IndexPath,并且对外界暴露出去,从而进行UI的刷新,

2. 具体实现

  1. 创建对象

其实就是记录了一下属性并且最后用 [self createTimer]创建了一个定时器

#pragma mark - 创建对象
+(instancetype)countDownManagerWithCountDownStartTime: (long)countDownStartTime
                                      andCountDownUnit: (double)countDownUnit
                                         andModelArray: (NSArray *)modelArray
                                       andModelDateKey: (NSString *)modelDateKey
                                  andModelCountDownKey: (NSString *)modelCountDownKey
                                      andModelDateType: (PYContDownManagerModelDateType)modelDateType
{
    return [[self alloc]initWithCountDownStartTime:countDownStartTime
                                  andCountDownUnit:countDownUnit
                                     andModelArray:modelArray
                                   andModelDateKey:modelDateKey
                              andModelCountDownKey:modelCountDownKey
                                  andModelDateType:modelDateType];
}
-(instancetype)initWithCountDownStartTime: (long)countDownStartTime
                          andCountDownUnit: (double)countDownUnit
                             andModelArray: (NSArray *)modelArray
                           andModelDateKey: (NSString *)modelDateKey
                      andModelCountDownKey: (NSString *)modelCountDownKey
                          andModelDateType: (PYContDownManagerModelDateType)modelDateType
{
    self = [super init];
    if (self) {
        self.countDownStartTime = countDownStartTime;
        self.countDownUnit = countDownUnit;
        self.modelArray = modelArray;
        self.modelDateKey = modelDateKey;
        self.modelCountDownKey = modelCountDownKey;
        self.modelDateType = modelDateType;
        if (!self.timer){
            [self createTimer];
        }
    }
    return self;
}
  1. 定时器的创建

这里用了GCD的定时器,子线程进行了数据的遍历处理,并在主线程进行了计时事件的回调。

//MARK: 计时器的创建
-(void)createTimer {
//0.创建队列
        dispatch_queue_t queue = self.queue;
        //1.创建GCD中的定时器
        /*
         第一个参数:创建source的类型 DISPATCH_SOURCE_TYPE_TIMER:定时器
         第二个参数:0
         第三个参数:0
         第四个参数:队列
         */
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
         self.timer = timer;
        //2.设置定时器
        /*
         第一个参数:定时器对象
         第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时
         第三个参数:间隔时间 GCD里面的时间最小单位为 纳秒
         第四个参数:精准度(表示允许的误差,0表示绝对精准)
         */
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, self.countDownUnit * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        //3.要调用的任务
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"GCD-----%@",[NSThread currentThread]);
            dispatch_async(self.queue, ^{
                [self lookingForATimelyModelArray:self.modelArray];
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (self.countdownDataFredbackWithBlock) {
                        self.countdownDataFredbackWithBlock();
                    }
                });
            });
        });
        //4.开始执行
        dispatch_resume(timer);
}
  1. 数据的处理

1.这个方法一直在子线程完成,对外界传来的model数组进行了遍历。并且对剩余时间进行了计算

-(void)lookingForATimelyModelArray: (NSArray *)modelArray {
    [modelArray enumerateObjectsUsingBlock:^(id  _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
        //如果依然是数组那么就在便利一次
        if ([[model class] isSubclassOfClass:NSClassFromString(@"NSArray")]) {
            self.column ++;
            [self lookingForATimelyModelArray:model];
        }
        //判断model中的关于时间类的类型
        NSString *dateValue = [model valueForKey:self.modelDateKey];
        long long dateNumber = dateValue.longLongValue;       
        //如果没有时间值
        if (!dateNumber) return;        
        //判断是否需要计算时间差
        if (self.modelDateType == PYContDownManagerModelDateType_OriginalTime){
            //时间差计算
           dateNumber = [self computationTimeDifferenceWithDateNumber:dateNumber];
        }
        //判断是否需要计时
        if (dateNumber <= self.countdownStartTime) {
            if ([[model valueForKey:self.modelCountDownKey] isKindOfClass:NSClassFromString(@"NSString")]) {
                [model setValue:@(dateNumber).description forKey:self.modelCountDownKey];
            }else if ([[model valueForKey:self.modelCountDownKey] isKindOfClass:NSClassFromString(@"NSNumber")]) {
                [model setValue:@(dateNumber) forKey:self.modelCountDownKey];
            }
        }
    }];
}
//MARK: 时间差的计算
-(long long)computationTimeDifferenceWithDateNumber: (long long)dateNumber {   
    NSTimeInterval timeInterval = [self.clientTime timeIntervalSince1970];
    return (dateNumber - timeInterval);
}

4.与外界联系的中转站

外界的传入的block代码块用属性储存,并在每个时间间隔过后调用

//MARK: 外部刷新UI的接口
- (void)countdownDataFredbackWithBlock: (void(^)())countdownDataFredbackWithBlock {
    self.countdownDataFredbackWithBlock = countdownDataFredbackWithBlock;
}

对model所在的位置IndexPath进行回调给外部,让外部进行UI的刷新

/**
 * 每个model的剩余值得改变都会调用
 * @param changeModelBlock 改变model时候的回调
 */
- (void)countDownWithChangeModelBlock: (void (^)(id model, NSIndexPath *index)) changeModelBlock;

5.取消与开启定时器

//MARK: 取消定时器
-(void)cancelTimer {
    dispatch_cancel(self.timer);
    self.timer = nil;
}
//MARK: 开启定时器
-(void)resumeTimer {
    if (!self.timer) {
        [self createTimer];
    }
}

6.上拉加载的卡顿现象的解决

/**
 * 在集成了MJRefresh之后,上拉加载时, 会出现卡顿,
 * 这是因为在子线程计算数据后,在回到主线程刷新UI时候,会强制把runloop由NSDefaultRunLoopMode转化为NSDefaultRunLoopMode,从而MJRefresh会自动回弹,
 * 此方法主要解决了这个问题。
 * 传入scrollView监听了偏移量,在滑动到底部的时候,会自动关闭Model的刷新和对外界的UI刷新,
 */
-(void)stopWenScrollViewScrollBottomWithTableView: (UIScrollView *)scrollView;

四、使用注意

对于UI的刷新

  1. 如果你要进行外部的TableView的刷新,那么请不要粗暴的reloadData,而是应该根据暴露出的IndexPath进行单一cell的刷新。
  1. 不要通过对cell的label.text的直接修改而达到倒计时的UI显示的效果。因为cell是会被重用的,你在外部拿到的label是一个地址,用这个方法进行刷新,你会发现,数据会错乱的一塌糊涂,毫无规律

上拉加载操作时候的卡顿现象

1.如果你用到的是这个方法进行刷新,那么在上拉操作的时候将不会造成UI的卡顿,但是,这个方法也会造成一些不必要的性能负担。因为大多情况下,其实很多cell 是不需要刷新的。

   //每个单位时间都会调用的方法
    [self.contDownManager countdownDataFredbackWithBlock:^{
        [self.tableView reloadData];
    }];

2.如果你用的这个方法刷新在上拉刷新时,UI界面会出现抽搐现象

//根据indexPath进行刷新
   [self.contDownManager countDownWithChangeModelBlock:^(id model, NSIndexPath *index) {
        //刷新
        [self.tableView reloadRowsAtIndexPaths:@[index] withRowAnimation:UITableViewRowAnimationNone];
    }];
```

>3.比较复杂的刷新方法。
`1、在tableView的cell中,定义一个NSString类型的属性countDownString,并对外暴露`
`2、在属性countDownString的setter方法中对显示计时信息的Label赋值 `
具体代码
cell中
```
 [self.contDwonManager countDownWithChangeModelBlock:^(HXBFinHomePageViewModel_PlanList *model, NSIndexPath *index) {
        if (weakSelf.finPlanListVMArray.count > index.row) {
            HXBFinancting_PlanListTableViewCell *cell = [weakSelf.planListTableView cellForRowAtIndexPath:index];
            cell.countDownString = model.countDownString;
        }
    }];
```





不懂的话就看示例代码把:[源代码请点这里这里](https://github.com/LiPengYue/CountDown)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容