Swift Combine:在可观察对象中使用计时器发布者

rod*_*elp 4 xcode swift swiftui xcode11 combine

在这个问题被标记为另一个问题的重复之前,我试图了解发布者的工作方式,因为它以我不期望的方式行事。

使用与前面提到的问题的答案相同的示例:

// Let's define the view model with my view...
import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
  private let cancellable: AnyCancellable?

  let intervalPublisher = Timer.TimerPublisher(
                            interval: 1.0, 
                            runLoop: .main, 
                            mode: .default)

  init() {
    self.cancellable = timerPublisher.connect() as? AnyCancellable
  }

  deinit {
    self.cancellable?.cancel()
  }
}

struct Clock : View {
  @EnvironmentObject var viewModel: TimerViewModel
  @State private var currentTime: String = "Initial"


  var body: some View {
    VStack {
      Text(currentTime)
    }
    .onReceive(timer.intervalPublisher) { newTime in
      self.currentTime = String(describing: newTime)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在这个阶段,我想做的就是我的视图模型直接发布值。我不想声明视图将接收这些类型的值。

理想情况下,我想把我的发布者变成一个正确的发布者......我认为下面的代码可以工作:

// Let's define the view model with my view...
import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
  private let cancellable: AnyCancellable?

  let intervalPublisher = Timer.TimerPublisher(
                            interval: 1.0, 
                            runLoop: .main, 
                            mode: .default)

  init() {
    self.cancellable = timerPublisher.connect() as? AnyCancellable
  }

  deinit {
    self.cancellable?.cancel()
  }
}

struct Clock : View {
  @EnvironmentObject var viewModel: TimerViewModel
  @State private var currentTime: String = "Initial"


  var body: some View {
    VStack {
      Text(currentTime)
    }
    .onReceive(timer.intervalPublisher) { newTime in
      self.currentTime = String(describing: newTime)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我做错了什么assign

为什么不触发?

编辑:环境对象是在SceneDelegate创建时钟视图后设置的。排除的代码附在下面:

// Let's define the view model with my view...
import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
  private let cancellable: AnyCancellable?
  private let assignCancellable: AnyCancellable?

  let intervalPublisher = Timer.TimerPublisher(
                            interval: 1.0, 
                            runLoop: .main, 
                            mode: .default)
 @Published var tick: String = "0:0:0"

  init() {
    cancellable = intervalPublisher.connect() as? AnyCancellable

    assignCancellable = intervalPublisher
                              .map { new in String(describing: new) }
                              .assign(to: \TimerViewModel.tick, on: self)
  }

  deinit {
    cancellable?.cancel()
    assignCancellable?.cancel()
  }
}

struct Clock : View {
  @EnvironmentObject var viewModel: TimerViewModel
  @State private var currentTime: String = "Initial"


  var body: some View {
    VStack {
      Text(currentTime)
      Text(viewModel.tick) // why doesn't this work?
    }
    .onReceive(timer.intervalPublisher) { newTime in
      self.currentTime = String(describing: newTime)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

Mic*_*mon 7

这与你的原版有点不同,但我希望没有什么重要的改变。

import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
    private var assignCancellable: AnyCancellable? = nil

    @Published var tick: String = "0:0:0"

    init() {
        assignCancellable = Timer.publish(every: 1.0, on: .main, in: .default)
            .autoconnect()
            .map { String(describing: $0) }
            .assign(to: \TimerViewModel.tick, on: self)
    }
}


struct ContentView: View {
    @State private var currentTime: String = "Initial"
    @ObservedObject var viewModel = TimerViewModel()

    var body: some View {
        VStack {
            Text(currentTime)
            Text(viewModel.tick) // why doesn't this work?
        }
        .onReceive(Timer.publish(every: 0.9, on: .main, in: .default).autoconnect(),
                perform: {
                    self.currentTime = String(describing: $0)
                }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

我将 viewModel 设为 ObservedObject 只是为了简化代码。

Timer.publish 方法与 autoconnect 一起使 Timer 更易于使用。我发现对多个订阅者使用同一个发布者会导致问题,因为第一次取消会杀死发布者。

我删除了 deinit() 因为取消似乎对订阅者来说是隐含的。

onReceive 和 viewModel 的更新之间存在干扰,但将 onReceive 更改为 0.9 解决了这个问题。

最后我发现Combine 中的print() 方法对于观察管道非常有用。

  • 只是为了让人们知道对 self 使用分配会创建一个保留周期 (3认同)