Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confused about disposables in regards to startWithNext on MutableProperties #3168

Closed
LunaCodeGirl opened this issue Sep 5, 2016 · 3 comments
Labels

Comments

@LunaCodeGirl
Copy link

LunaCodeGirl commented Sep 5, 2016

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?

@andersio
Copy link
Member

andersio commented Sep 5, 2016

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

@andersio
Copy link
Member

andersio commented Sep 5, 2016

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.

@LunaCodeGirl
Copy link
Author

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants