Swift 新增TableViewCell时原有Cell内DispatchSourceTimer秒数重置问题
问题原因分析
该问题是数据源设计、Cell复用逻辑、刷新逻辑共同导致的,核心原因如下:
- 数据源
MapTimer仅存储了倒计时的小时、分钟数值,没有存储固定的倒计时截止时间,每次调用setup方法时,都会基于当前时间重新计算expires,导致倒计时的秒数被重新计算,而小时、分钟因为和模型存储的数值一致,看起来没有变化。 - 新增Cell时你应该是调用了全局的
tableView.reloadData(),所有已展示的旧Cell都会重新执行setup方法,覆盖了原本的expires值,触发秒数重置。 - Cell的
setup方法执行时没有提前销毁旧的计时器,可能出现单个Cell绑定多个计时器同时回调的问题,也会引发时间跳变、资源泄漏。
修复方案
按以下步骤依次修改即可解决问题:
- 修改
MapTimer模型,预存倒计时截止时间,不要在Cell层计算截止时间
class MapTimer { // 原有属性保留 var hour: Int var min: Int // 新增截止时间戳属性,初始化时直接计算完成 var expiresTimestamp: TimeInterval init(hour: Int, minute: Int) { self.hour = hour self.min = minute let addComponents = DateComponents(hour: hour, minute: minute) self.expiresTimestamp = Calendar.current.date(byAdding: addComponents, to: Date())!.timeIntervalSince1970 } }
- 优化Cell的
setup方法,先清理旧计时器,直接使用模型预存的截止时间
func setup(with timer: MapTimer) { // 先销毁旧计时器,避免重复回调 stopwatch?.cancel() stopwatch = nil // 直接使用模型存储的截止时间,不需要重新计算 expires = Date(timeIntervalSince1970: timer.expiresTimestamp) stopwatch = DispatchSource.makeTimerSource(queue: .main) stopwatch?.schedule(deadline: .now(), repeating: 1.0) stopwatch?.setEventHandler { [weak self] in self?.countDownTimer() } stopwatch?.resume() }
- 优化TableView刷新逻辑,新增Cell时不要全局刷新,只插入对应新行即可
// 新增计时器后不要调用reloadData(),改为插入指定行 tableView.insertRows(at: [IndexPath(row: 新行索引, section: 0)], with: .automatic)
- 补全秒数的两位格式化,避免秒数小于10时显示异常,同时添加Cell复用的清理逻辑
@objc fileprivate func countDownTimer() { guard let expires = expires else { return } let timeLeft = Calendar.current.dateComponents([.hour,.minute,.second], from: Date(), to: expires) guard let hour = timeLeft.hour, let minute = timeLeft.minute, let second = timeLeft.second else { return } let hourFormatted = hour > 9 ? "\(hour)" : "0\(hour)" let minFormatted = minute > 9 ? "\(minute)" : "0\(minute)" // 补全秒数格式化 let secFormatted = second > 9 ? "\(second)" : "0\(second)" timerTime.text = "\(hourFormatted):\(minFormatted):\(secFormatted)" if hour == 0 && minute == 0 && second <= 29 { timerTime.textColor = UIColor(named: "Red") ?? .red } else { timerTime.textColor = .white } if hour == 0 && minute == 0 && second == 0 { stopwatch?.cancel() stopwatch = nil } } // 重写prepareForReuse,Cell复用时清理旧资源 override func prepareForReuse() { super.prepareForReuse() stopwatch?.cancel() stopwatch = nil timerTime.text = nil timerTime.textColor = .white }
内容的提问来源于stack exchange,提问作者user7684436
相关产品推荐
相关产品推荐

