Raymanman
Raymanman

Reputation: 23

Reactive Cocoa: trouble with switchToLatest in chain of operations

I'm having trouble disposing of one of the operations in a chained list of operations. I'm trying to use Reactive Cocoa to provide a stream of locations while the user is logged in and has granted access to location services.

I'm still pretty new to the world of Reactive Cocoa and functional reactive programming in general, but here's what I've got so far:

@weakify(self);

// signal of BOOL
RACSignal *loggedInSignal = [[self loggedInSignal] distinctUntilChanged];

// signal of CLAuthorizationStatus
RACSignal *currentStateSignal = [[self currentAuthorizationSignal] distinctUntilChanged];

// defer creation of the location stream signal until subscribed to
RACSignal *locationSignal = [RACSignal defer:^RACSignal *
{
    @strongify(self);
    // To get an uninterrupted stream of locations, just retry the stream if an error occurs
    return [[self locationsSignal] retry];
}];

RACSignal *locationServicesDisabledSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
    @strongify(self);
    [self locationServicesDisabled];
    [subscriber sendCompleted];
    return nil;
}];

self.disposable = [[[[[[loggedInSignal map:^id(NSNumber *loggedIn)
{
    return [loggedIn boolValue] ? currentStateSignal : [RACSignal empty];
}]
switchToLatest]
map:^id(NSNumber *currentStatus)
{
    if (CLAuthorizationStatusCanBeginMonitoring([currentStatus intValue]))
    {
        return locationSignal;
    }
    return locationServicesDisabledSignal;
}]
switchToLatest]
subscribeNext:^(CLLocation *location)
{
    @strongify(self);

    CLLocationCoordinate2D coord = location.coordinate;
    [self.output receivedNewLocationWithLatitude:coord.latitude
                                       longitude:coord.longitude
                                        accuracy:location.horizontalAccuracy
                                       timestamp:location.timestamp];
}]
asScopedDisposable];

This works as expected when the authorisation status changes, starting/stopping the location stream. But when logging out, the location stream continues to provide locations. It's as if authStateSignal is not disposed of, adjusting the authorisation status will still start/stop the stream, even though loggedInSignal had returned NO as its last value.

I tried this:

(previous code as above)...

    return locationServicesDisabledSignal;
}]
switchToLatest]
takeUntil:[loggedInSignal skip:1]]
subscribeNext:^(CLLocation *location)
{
(etc)...

after the second switchToLatest to stop that signal after the next log in/out, but besides feeling kind of "hack-ish", it stopped the location stream events after 1 log in/out, to never start them again. What is the proper method of handling this scenario?

Also, it feels like I'm using locationServicesDisabledSignal in the wrong way. I want a method to be called when authStateSignal returns a status indicating the location services are disabled, so that I can react accordingly, but putting the method call inside a [RACSignal createSignal:] block that way doesn't feel very Reactive Cocoa-ey. Any other tips on how to do any of what I'm trying the Reactive Cocoa way would be greatly appreciated too.

Upvotes: 1

Views: 244

Answers (1)

villy393
villy393

Reputation: 3063

The reason it isn't working out for you is that when you return a [RACSignal empty]; values will nor propogate through your chain because [RACSignal empty]; does not send any next events. So Try this:

 // signal of BOOL
RACSignal *loggedInSignal = [[self loggedInSignal] distinctUntilChanged];

// signal of CLAuthorizationStatus
RACSignal *currentStateSignal = [[self currentAuthorizationSignal] distinctUntilChanged];

// defer creation of the location stream signal until subscribed to
RACSignal *locationSignal = [RACSignal defer:^RACSignal *
{
    @strongify(self);
    // To get an uninterrupted stream of locations, just retry the stream if an error occurs
    return [[self locationsSignal] retry];
}];

RACSignal *locationServicesDisabledSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
    @strongify(self);
    [self locationServicesDisabled];
    [subscriber sendCompleted];
    return nil;
}];



self.disposable = [[[RACSignal combineLatest:@[loggedInSignal, currentStateSignal]] flattenMap: ^RACStream * (RACTuple *tuple){
    NSNumber* loggedIn = [tuple first];
    NSNumber* currentStatus = [tuple second];
    if([loggedIn boolValue] && CLAuthorizationStatusCanBeginMonitoring([currentStatus intValue])){
        return locationSignal
    }
    else {
        return locationServicesDisabledSignal
    }
} ]
subscribeNext:^(CLLocation *location)
{
@strongify(self);

CLLocationCoordinate2D coord = location.coordinate;
[self.output receivedNewLocationWithLatitude:coord.latitude
                                   longitude:coord.longitude
                                    accuracy:location.horizontalAccuracy
                                   timestamp:location.timestamp];
}]

Upvotes: 0

Related Questions