利用GCD 中的 dispatch_source_timer 给tableViewCell添加动态刷新的计时/倒计时功能

1、思路一(失败)

在设置好cell 里的内容之后在每个cell 返回时调用定时器事件,更新cell 内容,然后刷新整个表格。

- (void)didadida:(UITableViewCell *)cell indexPath:(NSIndexPath*)indexPath{
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            cell.textLabel.text = weakSelf.arrM[indexPath.row];
        }];
        [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    } else {
        // Fallback on earlier versions
    }
}

简单粗暴不奏效。
1、一秒刷新一次整个表格是不现实的,如果定时器更新频率为一秒60次呢?显然不合理
2、需要管理Timer,Timer的销毁时机不确定。

2、利用GCD的 dispatch_source_create 一个 DISPATCH_SOURCE_TYPE_TIMER

1、假如后台返回的活动结束时间在我们已经格式化为时间戳放在一个数组 self.arrM 里。
2、需要一个格式化时间的方法
3、(重点)每秒更新时间的方法
4、记得销毁定时器

@interface XXX()<UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, strong) NSMutableArray *timestampArr;

@end

//1、viewDidLoad里  self.timestampArr 三个活动结束的时间格式化为时间戳后放在数组里
NSArray *dateTimeArr = @[@"2019-04-15 17:30:00", @"2019-04-15 18:30:00", @"2019-04-15 20:30:00"];
    [dateTimeArr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSInteger tempTimestamp = [NSString timeToTimestamp:(NSString *)obj andFormatter:@"YYYY-MM-dd HH:mm:00"];
        [self.timestampArr addObject:@(tempTimestamp)];
    }];

//把活动结束时间戳和tableView 传递给目标更新函数,正常逻辑是在请求服务器返回时间列表后更新这些数据
    [self countPerSecond:self.tableView dataList:self.timestampArr];

//返回cell函数里
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIDT];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIDT];
    }
   
    cell.textLabel.text = [self caculateNowTime:[NSString getNowTimestamp] activeStartTime:[self.timestampArr[indexPath.row] longLongValue]];
    cell.tag = indexPath.row;
    cell.textLabel.textColor = [UIColor blackColor];
    NSLog(@"cell text: %@",self.timestampArr[indexPath.row]);
    return cell;
}

/**
 2 计算倒计时时间

 @param nowTime 现在的时间
 @param startTime 活动开始时间
 @return 根据距离活动时间的长短格式化时间显示
 */
- (NSString *)caculateNowTime:(NSInteger)nowTime activeStartTime:(NSInteger)startTime {
    NSTimeInterval timeInterval = nowTime - startTime;
    int days = (int)(timeInterval/(3600*24));
    int hours = (int)((timeInterval - days*24*3600)/3600);
    int minutes = (int)(timeInterval - days*24*3600 - hours*3600)/60;
    int seconds = timeInterval - days*24*360 - hours*3600 - minutes*60;
    NSString *dayStr, *hoursStr, *minutesStr, *secondStr;
    dayStr = [NSString stringWithFormat:@"%d",days];
    hoursStr = [NSString stringWithFormat:@"%d",hours];
    if (minutes < 10) {
        minutesStr = [NSString stringWithFormat:@"0%d",minutes];
    }else {
        minutesStr = [NSString stringWithFormat:@"%d",minutes];
    }
    
    if (seconds < 10) {
        secondStr = [NSString stringWithFormat:@"0%d",seconds];
    }else {
        secondStr = [NSString stringWithFormat:@"%d",seconds];
    }
    
    if (days) {
        return [NSString stringWithFormat:@"%@天 %@小时 %@分 %@秒",dayStr, hoursStr, minutesStr, secondStr];
    }
    return [NSString stringWithFormat:@"%@小时 %@分 %@秒",hoursStr,minutesStr,secondStr];
}

//3每秒更新时间
- (void)countPerSecond:(UITableView *)tableView dataList:(NSArray *)dataList {
    
    if (_timer == nil) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
        dispatch_source_set_event_handler(_timer, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                NSArray *cells = tableView.visibleCells;
                for (UITableViewCell *cell in cells) {
                    NSDate *phoneTime = [NSDate date];
                    NSInteger phoneTimeInteger = [phoneTime timeIntervalSince1970];
                    NSString *tempEndTime = dataList[cell.tag];
                    NSInteger endTime = tempEndTime.longLongValue;//
                    cell.textLabel.text = [self caculateNowTime:endTime activeStartTime:phoneTimeInteger];
                    
                    if ([cell.textLabel.text isEqualToString:@"活动已结束!"]) {
                        cell.textLabel.textColor = [UIColor redColor];
                    }else {
                        cell.textLabel.textColor = [UIColor orangeColor];
                    }
                }
            });
        });
        dispatch_resume(_timer);
    }
}
//4
- (void)destoryTimer {
    if (_timer) {
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}


这样的思路是可以走通的,但是还有许多需要优化的地方,如由于用的是时间差,计时数据可能是负的。
还有,如果用户改变了,自己手机的时间。我们再获取手机时间和结束时间做差也是不准确的,恶意用户可能用这个漏洞。
针对用户改变手机时间,我们可以在应用从后台进入前台时从新从后台获取一次活动结束时间。

时间,时间戳的转化函数

/**
 获取当前时间戳

 @return 当前时间戳
 */
+ (NSInteger)getNowTimestamp {
    //设置时间格式
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
    
    //设置时区
    NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
    [formatter setTimeZone:timeZone];
    NSDate *dateNow = [NSDate date];
    
    NSLog(@"设备当前的时间:%@",[formatter stringFromDate:dateNow]);
    
    //时间转时间戳
    NSInteger timeStamp = [[NSNumber numberWithDouble:[dateNow timeIntervalSince1970]] integerValue];
    NSLog(@"设备当前时间戳:%ld",(long)timeStamp);
    return timeStamp;
}


/**
 时间转时间戳

 @param formatTime 格式化的时间字符串
 @param format 时间格式
 @return 时间戳
 */
+ (NSInteger)timeToTimestamp:(NSString *)formatTime andFormatter:(NSString *)format {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [formatter setDateFormat:format];
    
    NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
    [formatter setTimeZone:timeZone];
    
    NSDate *date = [formatter dateFromString:formatTime];
    
    //时间转时间戳的方法
    NSInteger timestamp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue];
    NSLog(@"将某个时间转化成时间戳 : %ld",(long)timestamp);
    return timestamp;
}



/**
 时间戳转字符串

 @param timestamp 时间戳
 @param format 输出的时间格式
 @return 时间
 */
+ (NSString *)timeStampToTime:(NSInteger)timestamp andFormatter:(NSString *)format {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [formatter setDateFormat:format];
    
    NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
    [formatter setTimeZone:timeZone];
    NSDate *dateTime = [NSDate dateWithTimeIntervalSince1970:timestamp];
    
    NSLog(@"time : %@", dateTime);
    
    NSString *timeStr = [formatter stringFromDate:dateTime];
    
    return timeStr;
}


参考 https://www.jianshu.com/p/85909aabf058

原文地址:https://www.cnblogs.com/wjw-blog/p/10715015.html