1

I have a view that has to be able to rebind to a new view-model periodically: that means removing the old bindings as well as hooking up the new ones. I have a working solution, but it's not thread-safe and I wonder if there's a more idiomatic way to do the same:

var disposeBag = CompositeDisposable()

func bind(viewModel: TopicProgressViewModel) {
    disposeBag.dispose()
    disposeBag = CompositeDisposable()

    disposeBag += self.reactive.isHidden <~ viewModel.isHidden
    disposeBag += height <~ viewModel.height
    disposeBag += label.reactive.text <~ viewModel.label
    disposeBag += progress.reactive.progress <~ viewModel.progressFraction
}
Tikitu
  • 679
  • 6
  • 22

2 Answers2

2

You should use SerialDisposable.

var dispose = SerialDisposable()

func bind(viewModel: TopicProgressViewModel) {
    var disposeBag = CompositeDisposable()
    // serialDisposable will automatic dispose previous inner disposable
    dispose.inner = disposeBag
    disposeBag += self.reactive.isHidden <~ viewModel.isHidden
    disposeBag += height <~ viewModel.height
    disposeBag += label.reactive.text <~ viewModel.label
    disposeBag += progress.reactive.progress <~ viewModel.progressFraction
}
Tikitu
  • 679
  • 6
  • 22
Jeff
  • 166
  • 5
  • I didn't know about `SerialDisposable`, it looks like a helpful class. My only concern is that it seems like there will be a brief period in which the old and new bindings exist simultaneously. Maybe moving `dispose.inner = disposeBag` right below `var disposebag = CompositeDisposable()` would take care of this? – jjoelson Jun 15 '17 at 14:29
  • I agree we should dispose before binding, sadly SO won't let me edit just now (queue is full? or perhaps it's just being polite and I don't have the rep). Still I'm marking this the preferred answer for its simplicity. – Tikitu Jun 20 '17 at 10:08
0

You could try serializing the binds with a Signal, using scan to maintain the current binding state. Maybe something like this:

class TopicProgressView {
    private let bindInput: Observer<TopicProgressViewModel, NoError>
    private let bindSignal: Signal<CompositeDisposable, NoError>

    init() {
        private let (signal, observer) = Signal<TopicProgressViewModel, NoError>.pipe()

        bindInput = observer

        bindSignal = signal
            .scan(CompositeDisposable()) { [unowned self] disposeBag, newViewModel in
                disposeBag.dispose()

                let newDisposeBag = CompositeDisposable()

                newDisposeBag += self.reactive.isHidden <~ newViewModel.isHidden
                newDisposeBag += self.height <~ newViewModel.height
                newDisposeBag += self.label.reactive.text <~ newViewModel.label
                newDisposeBag += self.progress.reactive.progress <~ newViewModel.progressFraction

                return newDisposeBag
            }
    }

    func bind(viewModel: TopicProgressViewModel) {
        bindInput.send(value: viewModel)
    }
}

(I haven't tried this, but it seems workable to me).

jjoelson
  • 5,771
  • 5
  • 31
  • 51
  • I had to edit a little (currently in queue) to avoid [unowned self] too early in the init(), but otherwise this works beautifully: thanks! – Tikitu Jun 02 '17 at 07:57
  • Makes sense. In my opinion `scan` is a very underrated reactive operator. It's a very nice way to manage state. – jjoelson Jun 02 '17 at 15:58