SwiftUI多倒计时计时器异常:出现秒数延迟问题求助
问题分析与解决
核心问题
- 多Timer实例抢占主线程:每个
LotCard都创建独立的Timer.publish,大量Timer同时运行会导致主线程调度压力,引发倒计时延迟。 - 本地倒计时与服务器时间不同步:依赖本地Timer递减秒数,当SignalR推送新的结束时间时,无法快速对齐;且Timer取消逻辑有误(
autoconnect的Timer取消后无法自动重启)。 - 状态管理混乱:
@State的timeRemaining属于视图本地状态,与ViewModel状态分离,易出现数据不一致。
修复方案
1. 全局单例Timer替代多实例
不要给每个LotCard单独创建Timer,用一个全局Timer驱动所有倒计时更新,降低主线程开销:
// 全局Timer单例 class TimerManager: ObservableObject { static let shared = TimerManager() let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() }
2. 基于服务器结束时间计算剩余时长
把倒计时逻辑移到ViewModel,直接用服务器返回的结束时间戳计算剩余秒数,避免本地递减的误差:
// LotCardViewModel示例 class LotCardViewModel: ObservableObject { @Published var showTimer = true @Published var endTimestamp: TimeInterval // 服务器返回的结束时间戳(单位:秒) var timeRemaining: Int { let currentTime = Date().timeIntervalSince1970 let remaining = Int(endTimestamp - currentTime) return max(0, remaining) } }
3. 重构LotCard视图
移除本地Timer,订阅全局Timer,直接使用ViewModel计算的剩余时长,同时兼容SignalR更新:
struct LotCard: View { @ObservedObject var lotCardViewModel: LotCardViewModel let lotDto: LotDto let fnStartLotConnection: (String) -> Void // 订阅全局Timer @StateObject private var timerManager = TimerManager.shared var body: some View { HStack(spacing: 3){ Image(systemName: "stopwatch") if lotCardViewModel.showTimer { Text("\(lotCardViewModel.timeRemaining.toTimeString())") .font(.subheadline) .onReceive(timerManager.timer) { _ in if lotCardViewModel.timeRemaining <= 0 { lotCardViewModel.showTimer = false } } } } .onAppear { fnStartLotConnection(lotDto.uID) } // SignalR推送新的endTimestamp时,ViewModel的@Published会自动触发视图更新 } }
4. SignalR更新处理
当SignalR接收到新的结束时间时,直接更新ViewModel的endTimestamp即可:
// 在lotService的SignalR回调中 func handleLotUpdate(lotId: String, newEndTimestamp: TimeInterval) { if let index = getLotListDto.firstIndex(where: { $0.uID == lotId }) { getLotListDto[index].lotCardViewModel.endTimestamp = newEndTimestamp getLotListDto[index].lotCardViewModel.showTimer = true // 若之前已结束,重新显示计时器 } }
额外优化点
- 避免在
ForEach中使用$item传递绑定,改用@ObservedObject管理ViewModel,减少不必要的视图刷新。 - 给
toTimeString()方法添加边界处理,当剩余时间为0时显示"已结束"等提示文本。 - 全局Timer开销极低,无需在倒计时结束后取消订阅,通过
showTimer控制视图显示即可。
内容的提问来源于stack exchange,提问作者Jesus Fortuna
相关产品推荐
相关产品推荐

