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

Safe to dispose of a subscription this way? Better way? #963

Closed
ebaker355 opened this issue Nov 24, 2013 · 8 comments
Closed

Safe to dispose of a subscription this way? Better way? #963

ebaker355 opened this issue Nov 24, 2013 · 8 comments

Comments

@ebaker355
Copy link

In one of my view controllers, I am subscribing to a RACCommand that lives in a singleton object, like this (code is simplified):

- (void)viewDidAppear:(BOOL)animated {
    __block RACDisposable *disposable = [Singleton.command.executing subscribeNext:^(id x) {
        BOOL isExecuting = [x boolValue];
        if (isExecuting) {
            // The command is currently executing.
            // Display progress indicator.
        } else {
            // The command has finished executing.
            // Remove progress indicator (if shown) and do something with the command's data.

            // Unsubscribe from the command's executing signal.
            [disposable dispose];
            disposable = nil;
        }
    }
}

Basically, when this view controller is loaded and pushed onto my nav controller, the Singleton's command may or may not be executing. In fact, it is possible that this view controller may be popped, and a new instance pushed again on the nav controller stack, while the Singleton's command's original execution is still active in the background.

Since the RACCommand lives in a singleton, it is never dealloc'd, so it seems I have to unsubscribe manually from its executing signal.

I have tried takeUntilBlock:(... return !executing), however, this prevents my subscriber from being called when executing sends next -> NO; I cannot tell when the command finishes executing.

Is there another, more obvious/simpler, way to do this? Is it "safe" to unsubscribe the way I have shown here?

Also... thanks for ReactiveCocoa! It's awesome!

@joshaber
Copy link
Member

Unfortunately no, this (probably) isn't safe:

RACCommand.executing is documented to send the initial value on its first subscription. If it is NO, then you'd try to dispose of the disposable, but it'd still be nil since -subscribeNext: hasn't returned. You could solve this by using a RACSerialDisposable:

- (void)viewDidAppear:(BOOL)animated {
   RACDisposable *disposable = [Singleton.command.executing subscribeNext:^(id x) {
        BOOL isExecuting = [x boolValue];
        if (isExecuting) {
            // The command is currently executing.
            // Display progress indicator.
        } else {
            // The command has finished executing.
            // Remove progress indicator (if shown) and do something with the command's data.

            // Unsubscribe from the command's executing signal.
            [serialDisposable dispose];
            serialDisposable = nil;
        }
    }

    serialDisposable.disposable = disposable;
}

@ebaker355
Copy link
Author

Ah, ok... so, to be sure I understand, serialDisposable = disposable outside of the block allows the subscribeNext block to finish before disposing of the subscription. Is that correct?

I didn't know about the RACSerialDisposable class. In your example above, how/where is serialDisposable declared?

Thanks!

@joshaber
Copy link
Member

RACSerialDisposable will dispose of disposable if the serial disposable has been disposed of. So it's eliminating the potential ordering problems with the __block disposable.

serialDisposable should be a property on the class.

@ebaker355
Copy link
Author

Gotcha. Thanks!!

@Coneko
Copy link
Member

Coneko commented Nov 25, 2013

@joshaber I think you meant serialDisposable.disposable = disposable at the end there.

@joshaber
Copy link
Member

I did indeed! That's important. Thanks @Coneko.

@jspahrsummers
Copy link
Member

If I'm understanding correctly, you could get the same effect with:

[[[[Singleton.command.executing
    ignore:@NO]
    doNext:^(id _) {
        // The command is currently executing.
        // Display progress indicator.
    }]
    takeUntil:[Singleton.command.executing ignore:@YES]]
    subscribeCompleted:^{
        // The command has finished executing.
        // Remove progress indicator (if shown) and do something with the command's data.
    }];

This avoids any need to manage disposables by hand.

@ebaker355
Copy link
Author

Brilliant! This works perfectly in my scenario.

I am kicking off the Singleton's RACCommand in a table view controller's viewDidLoad method to download data (if needed). By the time viewDidAppear is called, if the command has not yet finished, then I want to display an indeterminate progress HUD. Once the data is fetched, then I remove the HUD and reload the table view.

At this point, the table view controller's UIRefreshControl can be used to reload the data, at which point I do not want the progress HUD to be displayed again, because the refresh control is visible while the data is refreshed. (I tried displaying the UIRefreshControl manually, rather than using a custom HUD indicator, but I encountered strange side effects.)

The RACCommand and data are stored in a Singleton because it is shared between multiple view controllers.

I guess I should have explained all that to begin with... Sorry!

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

No branches or pull requests

4 participants