El mejor enfoque Reactivo-Cacao para escribir un CLLocationManagerDelegate, que con poca frecuencia llegará a la location

Fondo

Estoy realmente emocionado por el marco de ReactiveCocoa y el potencial que tiene, así que he decidido que voy a morder la bala y escribir mi primera aplicación que lo use. En mi aplicación, ya escribí los diversos services y delegates, pero ahora necesito 'Reactivo-Cocoa-ise' para poder continuar con el lado real de las cosas GUI.

Dicho esto, para que entienda mejor esto, estoy escribiendo un código simple solo para probar conceptos. En este caso, escriba un contenedor para CLLocationManagerDelegate.

En la aplicación real, el caso de uso sería este:

  • 1) Cuando la aplicación se carga (viewDidLoad), luego 2) Intenta recuperar la location del dispositivo mediante
  • 2.1) si los services de location no están habilitados
  • 2.1.1) verifique el estado de la autorización y, si se le permite startMonitoringSignificantLocationChanges ,
  • 2.1.2) devuelve un error
  • 2.2) else (los services de location están habilitados)
  • 2.2.1) si la última location del administrador de location fue 'reciente' (6 horas o less), devuélvala
  • 2.2.2) else startMonitoringSignificantLocationChanges
  • 3) al devolver la location (ya sea de inmediato, oa través del locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations ), entonces también stopMonitoringSignificantLocationChanges
  • 4) Si el código que invoca el LocationManagerDelegate recibe un error y le pide al delegado un valor fijo 'pnetworkingeterminado'
  • 5) El siguiente fragment de código usa la location (ya sea buscada o pnetworkingeterminada) para iniciar y hacer un montón de llamadas en services de terceros (llamadas a WebServices, etc.)

Puede ver un arnés de testing en https://github.com/ippoippo/reactive-core-location-test

Inquietudes

El arnés 'funciona' en que se apaga y obtiene la location. Sin embargo, estoy realmente preocupado de que esté haciendo esto de la manera correcta.

Considere este código

 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]; }]; 

Preguntas

  • 1) ¿Cómo manejo los errores? Puedo usar catch que luego me da la oportunidad de pedirle al delegado una location pnetworkingeterminada. ¿Pero, entonces, estoy estancado en cómo volver a pasar esa location pnetworkingeterminada como señal? ¿O simplemente cambio mi método defaultLocation para devolver un RACSignal lugar de CLLocation ?
  • 2) Cuando devuelvo la location, ¿debería hacerse como sendNext o sendCompleted ? Actualmente está codificado como sendNext , pero parece que se debería hacer como sendCompleted .
  • 3) En realidad, la respuesta depende de cómo creo al delegado. Esta aplicación de testing crea un nuevo delegado cada vez que se carga la vista. Es algo que debo hacer, debo hacer que el delegado sea un singleton. Si es un singleton, entonces sendSiguiente parece lo correcto. Pero si implicaría entonces que muevo el código que tengo en la latestLocationSignal de latestLocationSignal en mi delegado en un método init en su lugar?

Las disculpas por la (s) pregunta (s) divagante (s), parecen estar dando vueltas en círculos en mi cabeza.

¿Cómo manejo los errores? Puedo usar catch, que luego me da la oportunidad de pedirle al delegado una location pnetworkingeterminada.

Estas tan cerca Use +[RACSignal return:] para crear una señal que simplemente envía su location pnetworkingeterminada:

 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); return [RACSignal return:defaultLocation]; }]; 

Cuando devuelvo la location, ¿debería hacerse como sendNext o sendCompleted? Actualmente está codificado como sendNext, pero parece ser algo que se haría como sendCompleted.

Para enviar datos de verdad, deberá usar -sendNext: Si eso es lo único que la señal está enviando, entonces también debería -sendCompleted después de eso. Pero si va a continuar enviando la location a medida que mejora la precisión o cambia la location, entonces no debería completarse todavía.

Más generalmente, las señales pueden enviar cualquier número de los next s (0 *), pero solo pueden completarse o fallar. Una vez que se envía la finalización o el error, se realiza la señal. No enviará más valores.

En realidad, la respuesta depende de cómo creo al delegado. Esta aplicación de testing crea un nuevo delegado cada vez que se carga la vista. Es algo que debo hacer, debo hacer que el delegado sea un singleton. Si es un singleton, entonces sendSiguiente parece lo correcto. Pero si implicaría entonces que muevo el código que tengo en la última location de señal en mi delegado en un método init en su lugar?

No estoy seguro de tener suficiente context para responder a esto, excepto para decir que los singletons nunca son la respuesta;) Crear un nuevo delegado cada vez que se carga la vista me parece razonable.

Es difícil responder preguntas amplias como esta porque no conozco tu layout casi tan bien como tú. Esperemos que estas respuestas ayuden un poco. Si necesita más aclaraciones, los ejemplos específicos son realmente útiles.

Parece ser muy fácil en ReactiveCocoa 3.0 y Swift:

 import ReactiveCocoa import LlamaKit class SignalCollector: NSObject, CLLocationManagerDelegate { let (signal,sink) = Signal<CLLocation, NoError>.pipe() let locationManager = CLLocationManager() func start(){ locationManager.delegate = self locationManager.desinetworkingAccuracy = kCLLocationAccuracyBest locationManager.startUpdatingLocation() } func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) { for item in locations { if let location = item as? CLLocation { sink.put(Event.Next(Box(location)) } } } } 

Y donde sea que necesite escuchar los events de location, use la señal con un observador:

 var locationSignals : [CLLocation] = [] signal.observe(next: { value in locationSignals.append(value)}) 

Tenga en count que, dado que es pre-alpha en el momento en que la syntax es probable que cambie.

Actualizando la respuesta anterior a ReactiveCocoa 4 y 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.desinetworkingAccuracy = 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) } } 

Y para escuchar los events:

  var locationSignals : [CLLocation] = [] signal.observeNext({ newLocation in locationSignals.append(newLocation) })