Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Reactive-Cocoa approach for writing a CLLocationManagerDelegate, which will infrequently fetch location

Background

I'm really excited by the ReactiveCocoa framework and the potential it has, so I've decided that I'm going to bite the bullet and write my first app using it. In my app, I've already written the various services and delegates, but I now need to 'Reactive-Cocoa-ise' them so that I can get on with actual GUI side of things.

That said, so that I better understand this, I'm writing a simple bit of code just to try out concepts. In this case, writing a wrapper for CLLocationManagerDelegate.

In the actual app, the use case would be this:

  • 1) When the app is loaded up (viewDidLoad) then 2) Attempt to fetch the location of the device by
  • 2.1) if location services not enabled then
  • 2.1.1) check authorisation status, and if allowed to startMonitoringSignificantLocationChanges,
  • 2.1.2) else return an error
  • 2.2) else (location services are enabled)
  • 2.2.1) if the location manager last location was 'recent' (6 hours or less) return that
  • 2.2.2) else startMonitoringSignificantLocationChanges
  • 3) when returning the location (either straight away, or via locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations), then we also stopMonitoringSignificantLocationChanges
  • 4) If code that calls on the LocationManagerDelegate receives and error, then ask the delegate for a fixed 'default' value
  • 5) Next piece of code then uses the location (either fetched, or default) to go off and do a bunch of calls on third party services (calls to WebServices etc)

You can see a test harness at https://github.com/ippoippo/reactive-core-location-test

Concerns

The harness 'works' in that it goes off and fetches the location. However, I'm really concerned I'm doing this in the right way.

Consider this code

RACSignal *fetchLocationSignal = [lmDelegate latestLocationSignal];

RACSignal *locationSignal = [fetchLocationSignal catch:^RACSignal *(NSError *error) {
    NSLog(@"Unable to fetch location, going to grab default instead: %@", error);
    CLLocation *defaultLocation = [lmDelegate defaultLocation];
    NSLog(@"defaultLocation = [%@]", defaultLocation);
    // TODO OK, so now what. I just want to handle the error and 
    // pass back this default location as if nothing went wrong???
    return [RACSignal empty];
}];

NSLog(@"Created signal, about to bind on self.locationLabel text");

RAC(self.locationLabel, text) = [[locationSignal filter:^BOOL(CLLocation *newLocation) {
    NSLog(@"Doing filter first, newLocation = [%@]", newLocation);
    return newLocation != nil;
}] map:^(CLLocation *newLocation) {
    NSLog(@"Going to return the coordinates in map function");
    return [NSString stringWithFormat:@"%d, %d", newLocation.coordinate.longitude, newLocation.coordinate.latitude];
}];

Questions

  • 1) How do I handle errors? I can use catch which then gives me the opportunity to then ask the delegate for a default location. But, I'm then stuck on how to then pass back that default location as a signal? Or do I just change my defaultLocation method to return a RACSignal rather than CLLocation??
  • 2) When I return the location, should it be done as sendNext or sendCompleted? Currently it's coded as sendNext, but it seems like something that would be done as sendCompleted.
  • 3) Actually, does the answer to that depend on how I create the Delegate. This test app creates a new Delegate each time the view is loaded. Is that something I should do, I should I make the Delegate a singleton. If it's a singleton, then sendNext seems the right thing to do. But if would then imply that I move the code that I have in latestLocationSignal in my delegate into an init method instead?

Apologies for the rambling question(s), just seem to be going around in circles in my head.

like image 398
ippoippo Software Avatar asked Feb 13 '23 15:02

ippoippo Software


1 Answers

Updating the answer above to ReactiveCocoa 4 and Swift 2.1:

import Foundation
import ReactiveCocoa

class SignalCollector: NSObject, CLLocationManagerDelegate {

let (signal,sink) = Signal<CLLocation, NoError>.pipe()

let locationManager = CLLocationManager()

  func start(){
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.startUpdatingLocation()
  }

  func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

    for item in locations {

        guard let location = item as CLLocation! else { return }         
        sink.sendNext(location)
    }
}

And to listen the events:

 var locationSignals : [CLLocation]  = []  
 signal.observeNext({ newLocation in
      locationSignals.append(newLocation)
    })
like image 90
scollaco Avatar answered Feb 16 '23 04:02

scollaco