SwiftUI Timer启停失效:页面切换后sink仍输出日志
问题分析
你的代码存在两个核心问题,导致调用cancel()后sink仍有日志输出:
- 订阅未彻底取消:
init中绑定的sink订阅始终关联着初始的Timer.publish实例,调用timer.upstream.connect().cancel()仅终止了Timer的连接,并未取消sink本身的订阅,旧的Timer实例及其订阅仍在内存中触发事件。 - Timer实例重复创建:每次调用
playPulseAnimation都会新建Timer实例,但旧的Timer订阅未被清理,导致多个Timer同时运行并输出日志。
解决方案
通过管理订阅生命周期来彻底控制Timer的启停,不再直接操作Timer的连接,而是通过订阅的创建与取消实现开关逻辑:
修改后的ViewModel代码
final class MyAnimationClass: ObservableObject { @Published var shouldPlayPulseAnimation = false // 按需生成Timer,不提前初始化 private var timer: AnyPublisher<Date, Never>? private var timerCancellable: AnyCancellable? func playPulseAnimation(isFirstTime: Bool = false) { let delay = isFirstTime ? 15.0 : 0.0 print("playPulseAnimation: 延迟\(delay)秒启动") DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in guard let self = self else { return } // 创建Timer并绑定订阅 self.timer = Timer.publish(every: 10, tolerance: 0.5, on: .main, in: .common) .autoconnect() .eraseToAnyPublisher() self.timerCancellable = self.timer? .sink(receiveValue: { [weak self] _ in print("TIMER SINK") self?.shouldPlayPulseAnimation = true }) } } func removePulseAnimation() { print("removePulseAnimation") shouldPlayPulseAnimation = false // 取消订阅并置空变量,确保Timer实例被释放 timerCancellable?.cancel() timerCancellable = nil timer = nil } }
关键改进点
- 按需订阅:仅在需要启动Timer时创建实例并绑定订阅,避免初始订阅残留。
- 显式清理:通过
timerCancellable?.cancel()直接终止订阅,同时置空相关变量,彻底销毁Timer实例。 - 避免重复运行:每次停止时完全清理旧的Timer与订阅,不会出现多个Timer同时触发的情况。
可选优化
如果需要确保每次启动前自动停止旧Timer,可调整playPulseAnimation逻辑:
func playPulseAnimation(isFirstTime: Bool = false) { // 启动前先清理旧Timer,防止重复运行 removePulseAnimation() let delay = isFirstTime ? 15.0 : 0.0 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in guard let self = self else { return } self.timer = Timer.publish(every: 10, tolerance: 0.5, on: .main, in: .common) .autoconnect() .eraseToAnyPublisher() self.timerCancellable = self.timer? .sink(receiveValue: { [weak self] _ in self?.shouldPlayPulseAnimation = true }) } }
内容的提问来源于stack exchange,提问作者Abhishek Thapliyal
相关产品推荐
相关产品推荐

