Skip to content

Commit

Permalink
Ensure subscriptions don't get canceled for intermittent errors
Browse files Browse the repository at this point in the history
  • Loading branch information
rob phillips committed Oct 11, 2015
1 parent 930ae15 commit 8b77585
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 17 deletions.
14 changes: 11 additions & 3 deletions LocationManager/INTULocationManager/INTULocationManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ - (void)processLocationRequests

for (INTULocationRequest *locationRequest in self.locationRequests) {
if (locationRequest.hasTimedOut) {
// Request has timed out, complete it
// Non-recurring request has timed out, complete it
[self completeLocationRequest:locationRequest];
continue;
}
Expand Down Expand Up @@ -708,8 +708,16 @@ - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *
{
INTULMLog(@"Location services error: %@", [error localizedDescription]);
self.updateFailed = YES;

[self completeAllLocationRequests];

for (INTULocationRequest *locationRequest in self.locationRequests) {
if (locationRequest.isRecurring) {
// Keep the recurring request alive
[self processRecurringRequest:locationRequest];
} else {
// Fail any non-recurring requests
[self completeLocationRequest:locationRequest];
}
}
}

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
Expand Down
2 changes: 1 addition & 1 deletion LocationManager/INTULocationManager/INTULocationRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ typedef NS_ENUM(NSInteger, INTULocationRequestType) {
@property (nonatomic, assign) NSTimeInterval timeout;
/** How long the location request has been alive since the timeout value was last set. */
@property (nonatomic, readonly) NSTimeInterval timeAlive;
/** Whether this location request has timed out (will also be YES if it has been completed). */
/** Whether this location request has timed out (will also be YES if it has been completed). Subcriptions can never time out. */
@property (nonatomic, readonly) BOOL hasTimedOut;
/** The block to execute when the location request completes. */
@property (nonatomic, copy, __INTU_NULLABLE) INTULocationRequestBlock block;
Expand Down
6 changes: 2 additions & 4 deletions LocationManager/LocationManagerExample/INTUViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ - (void)startLocationUpdateSubscription
strongSelf.statusLabel.text = [NSString stringWithFormat:@"'Location updates' subscription block called with Current Location:\n%@", currentLocation];
}
else {
// An error occurred, which causes the subscription to cancel automatically (this block will not execute again unless it is used to start a new subscription).
strongSelf.locationRequestID = NSNotFound;
// An error occurred
strongSelf.statusLabel.text = [strongSelf getErrorDescription:status];
}
}];
Expand All @@ -119,8 +118,7 @@ - (void)startMonitoringSignificantLocationChanges
strongSelf.statusLabel.text = [NSString stringWithFormat:@"'Significant changes' subscription block called with Current Location:\n%@", currentLocation];
}
else {
// An error occurred, which causes the subscription to cancel automatically (this block will not execute again unless it is used to start a new subscription).
strongSelf.locationRequestID = NSNotFound;
// An error occurred
strongSelf.statusLabel.text = [strongSelf getErrorDescription:status];
}
}];
Expand Down
54 changes: 50 additions & 4 deletions LocationManager/LocationManagerTests/INTULocationManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -233,16 +233,62 @@ @interface INTULocationManager (Spec) <CLLocationManagerDelegate>
});

describe(@"when the location manager fails", ^{
it(@"should complete all active requests", ^{
__block BOOL called = NO;
it(@"should complete all non-recurring requests", ^{
__block NSInteger singleCallbackCount = 0;
[subject requestLocationWithDesiredAccuracy:INTULocationAccuracyRoom timeout:0.1 delayUntilAuthorized:YES block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
called = YES;
singleCallbackCount++;
}];

NSError *error = [NSError errorWithDomain:@"domain" code:1337 userInfo:@{}];
[subject locationManager:subject.locationManager didFailWithError:error];

expect(called).will.beTruthy();
waitUntil(^(DoneCallback done) {
dispatch_after(0.5, dispatch_get_main_queue(), ^{
done();
});
});

expect(singleCallbackCount).to.equal(1);

// Fail it a second time and ensure it doesn't get called a second time
[subject locationManager:subject.locationManager didFailWithError:error];

waitUntil(^(DoneCallback done) {
dispatch_after(0.5, dispatch_get_main_queue(), ^{
done();
});
});

expect(singleCallbackCount).to.equal(1);
});

it(@"should keep alive all recurring requests", ^{
__block NSInteger recurringCallbackCount = 0;
[subject subscribeToLocationUpdatesWithDesiredAccuracy:INTULocationAccuracyBlock block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
recurringCallbackCount++;
}];

NSError *error = [NSError errorWithDomain:@"domain" code:1337 userInfo:@{}];
[subject locationManager:subject.locationManager didFailWithError:error];

waitUntil(^(DoneCallback done) {
dispatch_after(0.5, dispatch_get_main_queue(), ^{
done();
});
});

expect(recurringCallbackCount).to.equal(1);

// Fail it a second time and see if it's still called
[subject locationManager:subject.locationManager didFailWithError:error];

waitUntil(^(DoneCallback done) {
dispatch_after(0.5, dispatch_get_main_queue(), ^{
done();
});
});

expect(recurringCallbackCount).to.equal(2);
});
});

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ INTULocationManager *locMgr = [INTULocationManager sharedInstance];
```
### Subscribing to Continuous Location Updates
To subscribe to continuous location updates, use the method `subscribeToLocationUpdatesWithBlock:`. This method instructs location services to use the highest accuracy available (which also requires the most power). The block will execute indefinitely (until canceled), once for every new updated location regardless of its accuracy.
To subscribe to continuous location updates, use the method `subscribeToLocationUpdatesWithBlock:`. This method instructs location services to use the highest accuracy available (which also requires the most power). The block will execute indefinitely (even across errors, until canceled), once for every new updated location regardless of its accuracy.
If you do not need the highest possible accuracy level, you should instead use `subscribeToLocationUpdatesWithDesiredAccuracy:block:`. This method takes the desired accuracy level and uses it to control how much power is used by location services, with lower accuracy levels like Neighborhood and City requiring less power. Note that INTULocationManager will automatically manage the system location services accuracy level, including when there are multiple active location requests/subscriptions with different desired accuracies.
If an error occurs, the block will execute with a status other than `INTULocationStatusSuccess`, and the subscription will be canceled automatically.
If an error occurs, the block will execute with a status other than `INTULocationStatusSuccess`, and the subscription will be kept alive.
Here's an example:
```objective-c
Expand All @@ -116,15 +116,15 @@ INTULocationManager *locMgr = [INTULocationManager sharedInstance];
// A new updated location is available in currentLocation, and achievedAccuracy indicates how accurate this particular location is.
}
else {
// An error occurred, more info is available by looking at the specific status returned. The subscription has been automatically canceled.
// An error occurred, more info is available by looking at the specific status returned. The subscription has been kept alive.
}
}];
```

### Subscribing to Significant Location Changes
To subscribe to significant location changes, use the method `subscribeToSignificantLocationChangesWithBlock:`. This instructs location services to begin monitoring for significant location changes, which is very power efficient. The block will execute indefinitely (until canceled), once for every new updated location regardless of its accuracy. Note that if there are other simultaneously active location requests or subscriptions, the block will execute for every location update (not just for significant location changes). If you intend to take action only when the location has changed significantly, you should implement custom filtering based on the distance & time from the last received location.

If an error occurs, the block will execute with a status other than `INTULocationStatusSuccess`, and the subscription will be canceled automatically.
If an error occurs, the block will execute with a status other than `INTULocationStatusSuccess`, and the subscription will be kept alive.

Here's an example:
```objective-c
Expand All @@ -134,7 +134,7 @@ INTULocationManager *locMgr = [INTULocationManager sharedInstance];
// A new updated location is available in currentLocation, and achievedAccuracy indicates how accurate this particular location is.
}
else {
// An error occurred, more info is available by looking at the specific status returned. The subscription has been automatically canceled.
// An error occurred, more info is available by looking at the specific status returned. The subscription has been kept alive.
}
}];
```
Expand Down

0 comments on commit 8b77585

Please sign in to comment.