Reactivecocoa: Confused about disposables in regards to startWithNext on MutableProperties

Created on 5 Sep 2016  路  3Comments  路  Source: ReactiveCocoa/ReactiveCocoa

I guess I'm confused on how Disposables should work. I have a UITableViewCell subclass with a viewModel property. On didSet of the viewModel I'm calling the following function:

The Code

private func setupBindings() {

    if let friendViewModel = friendViewModel {
        disposableBindings += friendViewModel.statusLabel.producer.startWithNext({ status in
            self.statusLabel.text = status
        })
    }

}

The friendViewModel has a MutableProperty called statusLabel which looks like:

let statusLabel = MutableProperty<String>("")

That disposableBindings property is a CompositeDisposable, which I'm "disposing" in an overridden prepareForReuse() method, like:

override func prepareForReuse() {
    disposableBindings.dispose()
}

The Problem

The issue is that the statusLabel isn't being updated anytime after the first signal is being sent. I've debugged this and prepareForReuse() isn't being called. When I add logEvents() like ...producer.logEvents().startWithNext({... I get output in the console like:

[] Started fileName: /NotImportant/ButAllTheSame, functionName: setupBindings(), lineNumber: 60
[] Next Tap to invite... fileName: /NotImportant/ButAllTheSame, functionName: setupBindings(), lineNumber: 60
[] Interrupted fileName: /NotImportant/ButAllTheSame, functionName: setupBindings(), lineNumber: 60
[] Terminated fileName: /NotImportant/ButAllTheSame, functionName: setupBindings(), lineNumber: 60
[] Disposed fileName: /NotImportant/ButAllTheSame, functionName: setupBindings(), lineNumber: 60

I'm confused why an Interrupted event is being sent on the producer? This is a MutableProperty, so I'm not sure why Interrupted would be sent. I should add that through trial and error, I've found that just ignoring the disposable solves the problem:

friendViewModel.statusLabel.producer.logEvents().startWithNext({ status in
    self.statusLabel.text = status
})

However, as expected, I'm pretty sure this is inducing other problems where reused cells don't have the signals from previous invocations of setupBindings() disposed and they're changing the statusLabel text with values from the wrong view model's statusLabel MutableProperty producer.

What am I missing here?

question

Most helpful comment

@andersio thank you that's awesome! Fixes my problem and much cleaner.

Also, just FYI your explanation also helped. When I changed the code to instead be:

private var disposableBindings = CompositeDisposable()

private func setupBindings() {
        disposableBindings += friendViewModel.statusLabel.producer.startWithNext({ status in
            self.statusLabel.text = status
        })
    }
}

override func prepareForReuse() {
    disposableBindings.dispose()
    disposableBindings = CompositeDisposable()
}

The signal is persisting until cell reuse. I think the problem was from me reusing CompositeDisposable. I was treating it more like an NSCache or something.

Thanks again!

All 3 comments

A producer sends an interrupted event when it is manually interrupted (via the disposable).

If you dispose of the started producers (aka the produced signal), you would need to re-establish then.

Moreover, a disposed Disposable cannot be reused, and in your case the CompositeDisposable has to be replaced with a new instance on reuse.

That's said you may consider instead storing the view model in a MutableProperty and use flatMap to achieve the same goal.

// Assume viewModel is optional.
viewModel.producer.ignoreNil()
    .flatMap(.latest) { $0.statusLabel.producer }

This way you won't have to manage disposables on your own, while bindings are automatically handled when you update the view model property. You just need to bind these flattened producers once to their destination, e.g. in awakeFromNib.

P.S. You can flatten properties directly in Swift 3.0.

@andersio thank you that's awesome! Fixes my problem and much cleaner.

Also, just FYI your explanation also helped. When I changed the code to instead be:

private var disposableBindings = CompositeDisposable()

private func setupBindings() {
        disposableBindings += friendViewModel.statusLabel.producer.startWithNext({ status in
            self.statusLabel.text = status
        })
    }
}

override func prepareForReuse() {
    disposableBindings.dispose()
    disposableBindings = CompositeDisposable()
}

The signal is persisting until cell reuse. I think the problem was from me reusing CompositeDisposable. I was treating it more like an NSCache or something.

Thanks again!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sprynmr picture sprynmr  路  3Comments

v-silin picture v-silin  路  4Comments

BrettThePark picture BrettThePark  路  4Comments

lxian picture lxian  路  6Comments

porridgec picture porridgec  路  3Comments