Created
January 10, 2015 11:26
-
-
Save robpearson/eb3189514340d0f2c77d to your computer and use it in GitHub Desktop.
Reactive Location Service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Created by Rob Pearson on 28/08/13. | |
// Copyright (c) 2013 Maple Pixel Pty. Ltd. All rights reserved. | |
// | |
// To change the template use AppCode | Preferences | File Templates. | |
// | |
#import <Foundation/Foundation.h> | |
#import <CoreLocation/CoreLocation.h> | |
@class RACSignal; | |
@interface MPXLocationService : NSObject <CLLocationManagerDelegate> | |
+ (MPXLocationService *)sharedInstance; | |
- (BOOL)locationServicesEnabled; | |
- (CLAuthorizationStatus)authorizationStatus; | |
- (RACSignal *)requestAuthorizationSignal; | |
- (RACSignal *)locationSignal; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Created by Rob Pearson on 28/08/13. | |
// Copyright (c) 2013 Maple Pixel Pty. Ltd. All rights reserved. | |
// | |
// To change the template use AppCode | Preferences | File Templates. | |
// | |
#import "MPXLocationService.h" | |
#import "ReactiveCocoa.h" | |
NSString * const MPXLocationServiceErrorDomain = @"MPXLocationServiceErrorDomain"; | |
NSString * const MPXLocationNotAuthorisedErrorKey = @"MPXLocationNotAuthorisedErrorKey"; | |
@interface MPXLocationService () | |
@property(nonatomic, strong) CLLocationManager *locationManager; | |
@end | |
@implementation MPXLocationService | |
+ (MPXLocationService *)sharedInstance { | |
static MPXLocationService *sharedLocationService; | |
static dispatch_once_t pred; | |
dispatch_once(&pred, ^{ | |
sharedLocationService = [[MPXLocationService alloc] init]; | |
}); | |
return sharedLocationService; | |
} | |
- (id)init { | |
self = [super init]; | |
if (self) { | |
self.locationManager = [CLLocationManager new]; | |
self.locationManager.delegate = self; | |
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; | |
self.locationManager.distanceFilter = 100; // 100m accuracy | |
} | |
return self; | |
} | |
- (BOOL)locationServicesEnabled { | |
// TODO: Expose as signal as well | |
BOOL enabled = CLLocationManager.locationServicesEnabled; | |
return enabled; | |
} | |
- (CLAuthorizationStatus)authorizationStatus { | |
// TODO: Expose as signal as well | |
CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; | |
return status; | |
} | |
- (RACSignal *)requestAuthorizationSignal { | |
RACSignal *authorizationSignal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { | |
[[self rac_signalForSelector:@selector(locationManager:didChangeAuthorizationStatus:) fromProtocol:@protocol(CLLocationManagerDelegate)] subscribeNext:^(RACTuple *value) { | |
NSNumber *status = (NSNumber *) value[1]; // Ignore the location manager | |
[subscriber sendNext:status]; | |
[subscriber sendCompleted]; | |
}]; | |
CLAuthorizationStatus authorizationStatus = self.authorizationStatus; | |
if (authorizationStatus == kCLAuthorizationStatusNotDetermined) { | |
[self.locationManager requestWhenInUseAuthorization]; | |
} | |
else if (authorizationStatus == kCLAuthorizationStatusDenied || authorizationStatus == kCLAuthorizationStatusRestricted) { | |
NSError *error = [NSError errorWithDomain:MPXLocationServiceErrorDomain code:MPXLocationNotAuthorisedErrorKey userInfo:nil]; | |
[subscriber sendError:error]; | |
} | |
else { | |
[subscriber sendCompleted]; // All Good. Do Nothing | |
} | |
return nil; | |
}]; | |
return authorizationSignal; | |
} | |
- (RACSignal *)locationSignal { | |
// TODO: Review previous service code to add checks on last object to ensure that it's up-to-date and of the correct accuracy. | |
// TODO: Review how to restart the timer. I'm currently using take: 1 so it's not needed immediately. | |
RACSignal *locationSignal = [[self requestAuthorizationSignal] | |
then:^RACSignal * { | |
RACSignal *tmpSignal; | |
if (self.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse) { | |
tmpSignal = [RACSignal | |
createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { | |
[[self rac_signalForSelector:@selector(locationManager:didUpdateLocations:) fromProtocol:@protocol(CLLocationManagerDelegate)] subscribeNext:^(RACTuple *value) { | |
NSArray *locations = value[1]; // Ignore the location manager | |
[subscriber sendNext:locations.lastObject]; | |
}]; | |
[[self rac_signalForSelector:@selector(locationManager:didFailWithError:) fromProtocol:@protocol(CLLocationManagerDelegate)] subscribeNext:^(RACTuple *value) { | |
NSError *error = value[1]; // Ignore the location manager | |
if (error.domain == kCLErrorDomain && error.code == kCLErrorLocationUnknown) { | |
return; | |
} | |
[subscriber sendError:error]; | |
}]; | |
[self start]; | |
return [RACDisposable disposableWithBlock:^{ | |
[self stop]; | |
}]; | |
} | |
]; | |
} else { | |
tmpSignal = [RACSignal empty]; | |
} | |
return tmpSignal; | |
}]; | |
return locationSignal; | |
} | |
- (void)start { | |
[self.locationManager startUpdatingLocation]; | |
} | |
- (void)stop { | |
[self.locationManager stopUpdatingLocation]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If two or more subscriptions are created to
locationSignal
, and then one gets disposed, it will cause the other signals to stop. This could be avoided using a count of subscriptions. When the count goes to zero,stopUpdatingLocation
can be called.