You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI Timer启停失效:页面切换后sink仍输出日志

问题分析

你的代码存在两个核心问题,导致调用cancel()后sink仍有日志输出:

  1. 订阅未彻底取消:init中绑定的sink订阅始终关联着初始的Timer.publish实例,调用timer.upstream.connect().cancel()仅终止了Timer的连接,并未取消sink本身的订阅,旧的Timer实例及其订阅仍在内存中触发事件。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 23:10:59