Swift Combine PassThroughSubject发出意外事件

时间:2019-07-14 21:23:53

标签: swift swiftui combine

我目前正在使用Combine和SwiftUI,并已使用MVVM模式构建了一个原型应用程序。该应用程序利用计时器,并通过PassThroughSubject(不确定地)将控制该按钮的按钮状态绑定到视图模型。

按下按钮时,应切换状态变量的值;此值传递给视图模型的主题(使用.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))
    }
}

Viewmodel:

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
        ]

    }

}

2 个答案:

答案 0 :(得分:0)

在您的bindInput方法中,isTimerRunningSubject进行了订阅。我怀疑这不是您想要的,并且可能解释了您正在描述的怪异递归。也许您在某处缺少self.

奇怪的是,bindInputbindOutput都将所有流添加到cancellables数组中,因此它们将在其中两次。

希望这会有所帮助。

答案 1 :(得分:0)

此示例按预期工作,但是我在此过程中发现,无法在带有@Published的原始代码(内部结构定义输入和输出)中使用该模式。这会导致一些非常奇怪的错误(以及Playground中的BAD_ACCESS),并且是Combine beta 3中报告的错误。

final class ViewModel: BindableObject {

var didChange = PassthroughSubject<Void, Never>()

@Published var isEnabled = false

private var cancelled = [AnyCancellable]()

init() {
    bind()
}

private func bind() {
    let t = $isEnabled
        .map { _ in  }
        .eraseToAnyPublisher()
        .subscribe(didChange)

    cancelled += [t]
}
}

struct ContentView : View {
    @ObjectBinding var viewModel = ViewModel()

var body: some View {
    HStack {
        viewModel.isEnabled ? Text("Button ENABLED") : Text("Button disabled")
        Spacer()
        Toggle(isOn: $viewModel.isEnabled, label: { Text("Enable") })
    }
    .padding()
}
}