使用 Swift Combine 创建一个 Timer Publisher

Posted

技术标签:

【中文标题】使用 Swift Combine 创建一个 Timer Publisher【英文标题】:Create a Timer Publisher using Swift Combine 【发布时间】:2019-12-03 14:40:10 【问题描述】:

我一直在看Data Flow Through SwiftUI WWDC talk。他们有一张带有示例代码的幻灯片,其中他们使用连接到 SwiftUI 视图的 Timer 发布者,并随时间更新 UI。

我正在编写一些我想做完全相同的事情的代码,但无法弄清楚这个PodcastPlayer.currentTimePublisher 是如何实现的,然后连接到 UI 结构。我也看过所有关于 Combine 的视频。

我怎样才能做到这一点?

示例代码:

struct PlayerView : View 
  let episode: Episode
  @State private var isPlaying: Bool = true
  @State private var currentTime: TimeInterval = 0.0

  var body: some View 
    VStack  // ...
      Text("\(playhead, formatter: currentTimeFormatter)")
    
    .onReceive(PodcastPlayer.currentTimePublisher)  newCurrentTime in
      self.currentTime = newCurrentTime
    
  

【问题讨论】:

【参考方案1】:

这里有一个组合计时器的示例。我使用的是全局变量,但您当然应该使用适用于您的场景的任何变量(环境对象、状态等)。

import SwiftUI
import Combine

class MyTimer 
    let currentTimePublisher = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
    let cancellable: AnyCancellable?

    init() 
        self.cancellable = currentTimePublisher.connect() as? AnyCancellable
    

    deinit 
        self.cancellable?.cancel()
    


let timer = MyTimer()

struct Clock : View 
  @State private var currentTime: Date = Date()

  var body: some View 
    VStack 
      Text("\(currentTime)")
    
    .onReceive(timer.currentTimePublisher)  newCurrentTime in
      self.currentTime = newCurrentTime
    
  

【讨论】:

太棒了,谢谢。所以我猜在示例中他们将currentTimePublisher 作为静态类变量。 看起来像。我没有详细看那个练习。我发布的答案只是使用组合创建计时器的一种方法。也许还有其他人...... 是的。而且效果很好。将它作为他们和我的情况的静态变量是有意义的,因为它应该只存在一个计时器。 我认为 deinit 不需要,因为取消初始化时会自动调用 cancel()【参考方案2】:

使用ObservableObject

使用 Swift Combine 创建一个 Timer Publisher

class TimeCounter: ObservableObject 
    @Published var time = 0
    
    lazy var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true)  _ in self.time += 1 
    init()  timer.fire() 

就是这样!现在你只需要观察变化:

struct ContentView: View 
    @StateObject var timeCounter = TimeCounter()
    
    var body: some View 
        Text("\(timeCounter.time)")
    

【讨论】:

这和Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)有什么区别?【参考方案3】:

我实现了一个带有新功能的组合计时器,允许您在不同的时间间隔之间切换。

class CombineTimer 

    private let intervalSubject: CurrentValueSubject<TimeInterval, Never>

    var interval: TimeInterval 
        get 
            intervalSubject.value
        
        set 
            intervalSubject.send(newValue)
        
    

    var publisher: AnyPublisher<Date, Never> 
        intervalSubject
            .map 
                Timer.TimerPublisher(interval: $0, runLoop: .main, mode: .default).autoconnect()
            
            .switchToLatest()
            .eraseToAnyPublisher()
    

    init(interval: TimeInterval = 1.0) 
        intervalSubject = CurrentValueSubject<TimeInterval, Never>(interval)
    


要启动计时器,只需订阅publisher 属性。

SomeView()
    .onReceive(combineTimer.publisher)  date in
        // ...
    

您可以通过更改interval 属性切换到具有不同间隔的新计时器。

combineTimer.interval = someNewInterval

【讨论】:

如何取消您的 CombineTimer?【参考方案4】:

从 0 到 9 运行的计时器。

struct PlayerView : View 

    @State private var currentTime: TimeInterval = 0.0  
    @ObservedObject var player = PodcastPlayer()        
    var body: some View       
        Text("\(Int(currentTime))")
            .font(.largeTitle)
            .onReceive(player.$currentTimePublisher.filter  $0 < 10.0 )  newCurrentTime in
                self.currentTime = newCurrentTime
        
    

class PodcastPlayer: ObservableObject 

    @Published var currentTimePublisher: TimeInterval = 0.0     
    init() 
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true)  _ in
            self.currentTimePublisher += 1
        
    

【讨论】:

以上是关于使用 Swift Combine 创建一个 Timer Publisher的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift Combine 中创建自定义链?

如何使用 Combine + Swift 复制 PromiseKit 风格的链式异步流

Swift Combine - @Published 属性数组

如何在 Swift 中使用 Combine 读取 JSON 错误对象的属性值?

使用 Swift 和 Combine 链接 + 压缩多个网络请求

Swift/Combine - 将过滤后的对象分配给类的属性