Swift Combine:在可观察对象中使用计时器发布者 [英] Swift Combine: Using timer publisher in an observable object
问题描述
在此问题被标记为另一个问题的重复项之前,我试图了解发布商的行为方式,其行为出乎我的意料.
Before this question gets marked as duplicate of this other question, I am trying to understand how the publisher works as it behaves in a way I do not expect.
使用与前面提到的问题的答案相同的示例:
Using the same example as the answer from the question previously stated:
// 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)
}
}
}
在这个阶段,我要做的只是我的视图模型来直接发布值.我不需要声明视图将接收这些类型的值.
At this stage, all I wanted to do is my view model to publish the value directly. I don't want to have to declare the view will be receiving these sorts of values.
理想情况下,我想将我的发布者正确地发布为...尽管我可以使用以下代码:
Ideally, I want to turn my publisher into a published properly... I though that the following code would work:
// 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)
}
}
}
我的分配
我在做什么错了?
为什么不触发?
创建Clock视图后,将环境对象设置在 SceneDelegate
上.排除的代码附在下面:
the environment object was set on the SceneDelegate
once the Clock view was created. The code excluded is attached below:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let view = Clock().environmentObject(TimerViewModel())
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: view)
self.window = window
window.makeKeyAndVisible()
}
}
推荐答案
这与您的原始版本有所不同,但希望没有什么重要的改变.
This is a bit different to your original but nothing important is changed I hope.
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)
}
)
}
}
我将viewModel设置为ObservedObject只是为了简化代码.
I made viewModel an ObservedObject just to simplify the code.
Timer.publish方法与自动连接一起使Timer易于使用.我发现使用具有多个订阅者的同一发布者会导致问题,因为第一个取消操作会杀死发布者.
The Timer.publish method along with autoconnect make Timer easier to use. I have found that using the same publisher with multiple subscribers causes problems as the first cancel kills the publisher.
我删除了deinit(),因为取消似乎对订户是隐含的.
I removed the deinit() as the cancel seems to be implicit for subscribers.
onReceive和viewModel的更新之间存在干扰,但是将onReceive更改为0.9可以解决此问题.
There was an interference between updates from onReceive and viewModel but changing the onReceive to 0.9 fixed that.
最后,我发现Combine中的print()方法对于监视管道非常有用.
Finally I have discovered that the print() method in Combine is very useful for watching pipelines.
这篇关于Swift Combine:在可观察对象中使用计时器发布者的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!