CLLocationManager Singleton

Attempting to share location services across various controllres can lead to much duplicate code. If you're simply interested in receiving updates about the user's current location, building a central location for retrieving this information can be quite useful.

In my app, I settled on building a Singleton that wraps the CLLocationManager. This ends up being fairly simple and looks like this...

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

@interface LocationService : NSObject <CLLocationManagerDelegate>

+(LocationService *) sharedInstance;

@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) CLLocation *currentLocation;

- (void)startUpdatingLocation;

@end

In the above interface we create a class method called sharedInstance. I discuss the singleton pattern in a previous article.

But more importantly, we have implement the CLLocationManagerDelegate directly on this class. Therefore, this class will receieve the updates from the LocationManager and will update the currentLocation property.

The implementation code looks something like this:

#import "LocationService.h"

@implementation LocationService

+(LocationService *) sharedInstance
{
    static LocationService *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc]init];
    });
    return instance;
}

- (id)init {
    self = [super init];
    if(self != nil) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        self.locationManager.distanceFilter = 100; // meters
        self.locationManager.delegate = self;
    }
    return self;
}

- (void)startUpdatingLocation
{
    NSLog(@"Starting location updates");
    [self.locationManager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error
{
    NSLog(@"Location service failed with error %@", error);
}

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray*)locations
{
    CLLocation *location = [locations lastObject];
    NSLog(@"Latitude %+.6f, Longitude %+.6f\n",
          location.coordinate.latitude,
          location.coordinate.longitude);
    self.currentLocation = location;
}
@end

In this service, we implement the singleton in the class sharedInstance method.

In the initialization method we create an initialize the CLLocationManager instance.

We also implement any delegate methods in this class. Of importance is that on the locationManager:didUpdateLocations method, we set the currentLocation property that can be used by other services.

So that begs the question, how do we use the service? Typically, we would access the single method by using the sharedInstance static method. This method would then provide us with the current location...

[LocationService sharedInstance].currentLocation

However, because CLLocationManager is async, we can't just assume that we have a value in currentLocation. We need a way to observe this property.

A nice article describes the techniques we can use for observing objects. Enter Key Value Observing. A simple technique for doing example what we want, monitoring a property.

In our case, the consumer of our service, a controller for instance, simply adds a listener for changes to the currentLocation property.

[[LocationService sharedInstance] addObserver:self forKeyPath:@"currentLocation" options:NSKeyValueObservingOptionNew context:nil];

This will monitor the Singleton object and watch the currentLocation property for changes. Activity will be fired in the observeValueForKeyPath method...

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object  change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:@"currentLocation"]) {
        // do some stuff
    }
}

We can filter what "key" and what "object" did the firing in conditional statements and execute our code accordingly.

That's all there is too it!

comments powered by Disqus