- 左边是输出台,右边是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. 常用的方法:
- 这个方法是用于外界进行UI界面刷新的。在这里面可以进行model数组的遍历,找出小于0的model,在做相关UI操作(默认回到了主线程)
/**
*没次单位时间过后就会掉一次,可以在这里刷新UI(已经回到了主线程)
*@param countdownDataFredbackWithBlock 给外界提供了model (这时候model已经赋值成功了),
*/
-(void)countdownDataFredbackWithBlock: (void(^)(id model,long long CountDown))countdownDataFredbackWithBlock;
- 取消定时器:在大多数情况下,点击cell后会跳转到相应的二级界面,那么我们就有必要把定时器关掉
/**
*取消定时器
*/
-(void)cancelTimer;
- 定时器的重启: 同样,在回调到当前界面的时候,定时器应该是重新开启的(其实内部是重新创建了一个定时器)
/**
*开启定时器
*/
-(void)resumeTimer;
3.常用的属性
1.很多情况下,服务器请求回来的时间与本地时间并不一致,这样会导致倒计时的不准确,所以我们要把本地参考的当前时间与服务器的当前时间作统一,把服务器时间传入这里,就可以来更正倒计时的准确度
如果对服务器与客户端时间矫正问题有疑问请看这里
/**
* 客户端时间,默认为手机的当前时间。如果有偏差可以在这里调整
*/
@property (nonatomic,strong) NSDate *clientTime;
三、具体实现的逻辑.m
1. 逻辑
- 类的内部创建了一个定时器。每次定时器执行后都会遍历model数组,并且判断是否应该倒计时,如果需要倒计时,那么就通过kvc给model的剩余时间属性赋值。
- 每隔一个单位时间间隔,就会执行回调方法的block
- 根据对model数组的遍历生成IndexPath,并且对外界暴露出去,从而进行UI的刷新,
2. 具体实现
- 创建对象
其实就是记录了一下属性并且最后用
[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;
}
- 定时器的创建
这里用了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.这个方法一直在子线程完成,对外界传来的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的刷新
- 如果你要进行外部的TableView的刷新,那么请不要粗暴的reloadData,而是应该根据暴露出的IndexPath进行单一cell的刷新。
- 不要通过对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)