I guess I'm confused on how Disposable
s should work. I have a UITableViewCell
subclass with a viewModel
property. On didSet
of the viewModel
I'm calling the following function:
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 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?
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!
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:
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!