倒计时工具类: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的剩余时间属性赋值。
  2. 每隔一个单位时间间隔,就会执行回调方法的block
  3. 根据对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的刷新。
  2. 不要通过对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;
}
}];
“`

不懂的话就看示例代码把:源代码请点这里这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值