SwiftUI - 可选计时器,重置和重新创建

SwiftUI - Optional Timer, reset and recreate

通常,我会使用一个可选变量来保存我的 Timer 引用,因为能够 invalidate 并将其设置为 nil 很好重新创建之前。

我正在尝试使用 SwiftUI 并想确保我这样做是正确的...

我声明为:

@State var timer:Publishers.Autoconnect<Timer.TimerPublisher>? = nil

后来我:

self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

要驱动 UI 文本控件,我使用:

.onReceive(timer) { time in
    print("The time is now \(time)")
}

这个 Combine 类型的 Timer 无效和重新创建的正确方法是什么?

我读过一个应该调用:

self.timer.upstream.connect().cancel()

但是,我还需要 invalidate 还是直接 nil out?

没必要扔掉TimerPublisher本身。 Timer.publish 创建一个 Timer.TimerPublisher 实例,它与所有其他发布者一样,仅在您创建对其的订阅时才开始发出值 - 一旦订阅关闭它就会停止发出值。

因此,无需重新创建 TimerPublisher,您只需要在需要时重新创建对它的订阅。

所以在声明中分配 Timer.publish,但不要 autoconnect()。每当您想启动计时器时,对其调用 connect 并将 Cancellable 保存在实例 属性 中。然后,每当您想停止计时器时,在 Cancellable 上调用 cancel 并将其设置为 nil.

您可以在下面找到一个完整的工作视图,其中包含一个预览,它会在 5 秒后启动计时器,每秒更新一次视图,并在 30 秒后停止流式传输。

这可以通过将发布者和订阅存储在视图模型中并将其注入视图来进一步改进。

struct TimerView: View {
    @State private var text: String = "Not started"

    private var timerSubscription: Cancellable?

    private let timer = Timer.publish(every: 1, on: .main, in: .common)

    var body: some View {
        Text(text)
            .onReceive(timer) {
                self.text = "The time is now \([=10=])"
            }
    }

    mutating func startTimer() {
        timerSubscription = timer.connect()
    }

    mutating func stopTimer() {
        timerSubscription?.cancel()
        timerSubscription = nil
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        var timerView = TimerView()
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            timerView.startTimer()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            timerView.stopTimer()
        }
        return timerView
    }
}

使用视图模型,您甚至不需要向视图公开 TimerPublisher(或任何 Publisher),而只需更新 @Published 属性 并将其显示在视图的 body 中。这使您能够将 timer 声明为 autoconnect,这意味着您不需要手动调用 cancel,您可以简单地 nil 出订阅引用来停止定时器。

class TimerViewModel: ObservableObject {
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    private var timerSubscription: Cancellable?

    @Published var time: Date = Date()

    func startTimer() {
        timerSubscription = timer.assign(to: \.time, on: self)
    }

    func stopTimer() {
        timerSubscription = nil
    }
}

struct TimerView: View {
    @ObservedObject var viewModel: TimerViewModel

    var body: some View {
        Text(viewModel.time.description)
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        let viewModel = TimerViewModel()
        let timerView = TimerView(viewModel: viewModel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            viewModel.startTimer()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            viewModel.stopTimer()
        }
        return timerView
    }
}