【发布时间】:2019-07-14 21:23:53
【问题描述】:
我目前正在使用 Combine 和 SwiftUI,并使用 MVVM 模式构建了一个原型应用程序。该应用程序使用了一个计时器,并且控制它的按钮的状态被(不雅地)绑定到使用 PassThroughSubject 的视图模型。
当按钮被按下时,这应该会切换状态变量的值; this 的值被传递给视图模型的主题(使用 .send),它应该在每次按下按钮时发送一个事件。但是,当多个事件被发送到主题时,似乎存在递归或同样奇怪的事情,并且在 UI 没有更新的情况下导致运行时崩溃。
这有点令人费解,我不确定这是Combine 中的错误还是我遗漏了什么。任何指针将不胜感激。下面的代码 - 我知道它很乱 ;-) 我已将其精简为看起来相关的内容,但如果您需要更多,请告诉我。
查看:
struct ControlPanelView : View {
@State private var isTimerRunning = false
@ObjectBinding var viewModel: ControlPanelViewModel
var body: some View {
HStack {
Text("Case ID") // replace with binding to viewmode
Spacer()
Text("00:00:00") // repalce with binding to viewmodel
Button(action: {
self.isTimerRunning.toggle()
self.viewModel.apply(.isTimerRunning(self.isTimerRunning))
print("Button press")
}) {
isTimerRunning ? Image(systemName: "stop") : Image(systemName: "play")
}
}
// .onAppear(perform: { self.viewModel.apply(.isTimerRunning(self.isTimerRunning)) })
.font(.title)
.padding(EdgeInsets(top: 0, leading: 32, bottom: 0, trailing: 32))
}
}
视图模型:
final class ControlPanelViewModel: BindableObject, UnidirectionalDataType {
typealias InputType = Input
typealias OutputType = Output
private let didChangeSubject = PassthroughSubject<Void, Never>()
private var cancellables: [AnyCancellable] = []
let didChange: AnyPublisher<Void, Never>
// MARK:- Input
...
private let isTimerRunningSubject = PassthroughSubject<Bool, Never>()
....
enum Input {
...
case isTimerRunning(Bool)
...
}
func apply(_ input: Input) {
switch input {
...
case .isTimerRunning(let state): isTimerRunningSubject.send(state)
...
}
}
// MARK:- Output
struct Output {
var isTimerRunning = false
var elapsedTime = TimeInterval(0)
var concernId = ""
}
private(set) var output = Output() {
didSet { didChangeSubject.send() }
}
// MARK:- Lifecycle
init(timerService: TimerService = TimerService()) {
self.timerService = timerService
didChange = didChangeSubject.eraseToAnyPublisher()
bindInput()
bindOutput()
}
private func bindInput() {
utilities.debugSubject(subject: isTimerRunningSubject)
let timerToggleStream = isTimerRunningSubject
.subscribe(isTimerRunningSubject)
...
cancellables += [
timerToggleStream,
elapsedTimeStream,
concernIdStream
]
}
private func bindOutput() {
let timerToggleStream = isTimerRunningSubject
.assign(to: \.output.isTimerRunning, on: self)
...
cancellables += [
timerToggleStream,
elapsedTimeStream,
idStream
]
}
}
【问题讨论】:
-
我不是专家,所以请考虑到这一点。是什么引起了我的注意你的按钮。为什么它要更新 both
isTimerRunning和...在您的viewModel中的某些内容?为什么不只是 (1) 摆脱apply和“本地”更新,而是 (2) 直接更新您的viewModel.isTimerRunning?如果您不想直接在按钮操作中执行此操作,请在您的视图中创建一个本地func并从那里调用它。底线,如果实际所有者是模型,为什么你的ControlPanelView的isTimerRunning存在? -
我不介意你所说的“凌乱”的代码,但请考虑添加缺失的部分,以便其他人实际编译和复制它。这将增加您获得正确答案的机会,并可能获得解决方案。还要注意,很多时候,创建一个最小的例子来展示一个问题,会引导你取得突破。您甚至可能不需要一开始就发布您的问题。
-
感谢你们两位 cmets - 这是有用的反馈。是的,视图模型绑定破坏了这种模式的对象,并且可能引入了一些意想不到的行为。我会在第一个实例中尝试纠正这个问题,它可以解决问题。创建一个最小示例的建议是一个绝妙的主意,我肯定会在此范围内运行。