Cómo sincronizar CoreData y un servicio web REST de forma asincrónica y al mismo tiempo propagar correctamente cualquier error REST en la interfaz de usuario

85

Hola, estoy trabajando en la capa de modelo para nuestra aplicación aquí.

Algunos de los requisitos son así:

  1. Debería funcionar en iPhone OS 3.0+.
  2. La fuente de nuestros datos es una aplicación RESTful Rails.
  3. Deberíamos almacenar en caché los datos localmente usando Core Data.
  4. El código del cliente (nuestros controladores de interfaz de usuario) debe tener el menor conocimiento posible sobre las cosas de la red y debe consultar / actualizar el modelo con la API de datos centrales.

Revisé la sesión 117 de la WWDC10 sobre la creación de una experiencia de usuario impulsada por el servidor, pasé un tiempo revisando los marcos Objective Resource , Core Resource y RestfulCoreData .

El marco de Objective Resource no habla con Core Data por sí solo y es simplemente una implementación de cliente REST. Core Resource y RestfulCoreData asumen que usted habla con Core Data en su código y ellos resuelven todos los aspectos prácticos en segundo plano en la capa del modelo.

Todo parece estar bien hasta ahora e inicialmente pensé que Core Resource o RestfulCoreData cubrirán todos los requisitos anteriores, pero ... Hay un par de cosas que aparentemente ninguna de ellas resuelve correctamente:

  1. El hilo principal no debe bloquearse mientras se guardan las actualizaciones locales en el servidor.
  2. Si la operación de guardado falla, el error debe propagarse a la interfaz de usuario y no debe guardarse ningún cambio en el almacenamiento de datos principales local.

Core Resource emite todas sus solicitudes al servidor cuando llama - (BOOL)save:(NSError **)errora su Contexto de objeto administrado y, por lo tanto, puede proporcionar una instancia NSError correcta de las solicitudes subyacentes al servidor que fallan de alguna manera. Pero bloquea el hilo de llamada hasta que finaliza la operación de guardar. FALLAR.

RestfulCoreData mantiene sus -save:llamadas intactas y no introduce ningún tiempo de espera adicional para el hilo del cliente. Simplemente vigila NSManagedObjectContextDidSaveNotificationy luego emite las solicitudes correspondientes al servidor en el controlador de notificaciones. Pero de esta manera la -save:llamada siempre se termina con éxito (así, teniendo en cuenta los datos de la base es bien con los cambios guardados) y el código de cliente que llama en realidad no tiene manera de saber el ahorro podría haber fallado para propagar al servidor debido a alguna 404o 421o lo que sea Ocurrió un error del lado del servidor. Y aún más, el almacenamiento local pasa a tener los datos actualizados, pero el servidor nunca se entera de los cambios. FALLAR.

Entonces, estoy buscando una posible solución / prácticas comunes para lidiar con todos estos problemas:

  1. No quiero que el hilo de llamada se bloquee en cada -save:llamada mientras ocurren las solicitudes de red.
  2. Quiero de alguna manera recibir notificaciones en la interfaz de usuario de que alguna operación de sincronización salió mal.
  3. Quiero que el guardado de Core Data también falle si fallan las solicitudes del servidor.

¿Algunas ideas?

eploko
fuente
1
Vaya, no tienes idea de los problemas que me has salvado al hacer esta pregunta. Actualmente implementé mi aplicación para que el usuario espere los datos cada vez que hago una llamada (aunque sea a un servicio web .NET). He estado pensando en una forma de hacerlo asincrónico, pero no pude averiguar cómo hacerlo. ¡Gracias por todos los recursos que ha proporcionado!
Tejaswi Yerukalapudi
Excelente pregunta, gracias.
Justin
El enlace al recurso principal está roto, ¿alguien sabe dónde está alojado ahora?
El recurso principal todavía está alojado en GitHub aquí: github.com/mikelaurence/CoreResource
eploko
Y el sitio original también se puede encontrar en gitHub: github.com/mikelaurence/coreresource.org
eploko

Respuestas:

26

Realmente debería echar un vistazo a RestKit ( http://restkit.org ) para este caso de uso. Está diseñado para resolver los problemas de modelado y sincronización de recursos JSON remotos con una caché local respaldada por Core Data. Admite un modo fuera de línea para trabajar completamente desde la caché cuando no hay una red disponible. Toda la sincronización se produce en un subproceso en segundo plano (acceso a la red, análisis de carga útil y fusión de contexto de objeto administrado) y hay un amplio conjunto de métodos delegados para que pueda saber qué está sucediendo.

Blake Watters
fuente
18

Hay tres componentes básicos:

  1. La acción de la interfaz de usuario y persistencia del cambio a CoreData
  2. Persistir ese cambio hasta el servidor
  3. Actualizar la interfaz de usuario con la respuesta del servidor

Una NSOperation + NSOperationQueue ayudará a mantener ordenadas las solicitudes de red. Un protocolo delegado ayudará a sus clases de UI a comprender en qué estado se encuentran las solicitudes de red, algo como:

@protocol NetworkOperationDelegate
  - (void)operation:(NSOperation *)op willSendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op didSuccessfullySendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op encounteredAnError:(NSError *)error afterSendingRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
@end

El formato del protocolo, por supuesto, dependerá de su caso de uso específico, pero esencialmente lo que está creando es un mecanismo mediante el cual los cambios pueden "enviarse" a su servidor.

A continuación, está el bucle de la interfaz de usuario a considerar, para mantener su código limpio, sería bueno llamar a save: y hacer que los cambios se envíen automáticamente al servidor. Puede utilizar las notificaciones NSManagedObjectContextDidSave para esto.

- (void)managedObjectContextDidSave:(NSNotification *)saveNotification {
  NSArray *inserted = [[saveNotification userInfo] valueForKey:NSInsertedObjects];
  for (NSManagedObject *obj in inserted) {
    //create a new NSOperation for this entity which will invoke the appropraite rest api
    //add to operation queue
  }

  //do the same thing for deleted and updated objects
}

La sobrecarga computacional para insertar las operaciones de red debería ser bastante baja, sin embargo, si crea un retraso notable en la interfaz de usuario, simplemente puede tomar los identificadores de entidad de la notificación de guardado y crear las operaciones en un hilo de fondo.

Si su API REST admite el procesamiento por lotes, incluso podría enviar toda la matriz a la vez y luego notificarle a la interfaz de usuario que se sincronizaron varias entidades.

El único problema que preveo, y para el que no existe una solución "real", es que el usuario no querrá esperar a que sus cambios se envíen al servidor para poder realizar más cambios. El único buen paradigma con el que me he encontrado es que le permite al usuario seguir editando objetos y agrupar sus ediciones cuando sea apropiado, es decir, no presiona en cada notificación de guardado.

ImCazaWabbits
fuente
2

Esto se convierte en un problema de sincronización y no es fácil de resolver. Esto es lo que haría: en la interfaz de usuario de su iPhone, use un contexto y luego, con otro contexto (y otro hilo), descargue los datos de su servicio web. Una vez que esté todo allí, realice los procesos de sincronización / importación recomendados a continuación y luego actualice su interfaz de usuario después de que todo se haya importado correctamente. Si las cosas van mal al acceder a la red, simplemente revierte los cambios en el contexto que no es la interfaz de usuario. Es un montón de trabajo, pero creo que es la mejor manera de abordarlo.

Datos básicos: importación de datos de forma eficiente

Datos básicos: gestión del cambio

Core Data: Multi-Threading con Core Data

David Weiss
fuente
0

Necesita una función de devolución de llamada que se ejecutará en el otro subproceso (en el que ocurre la interacción real del servidor) y luego coloque el código de resultado / información de error en datos semi-globales que serán verificados periódicamente por el subproceso de IU. Asegúrese de que el cableado del número que sirve como bandera sea atómico o tendrá una condición de carrera; digamos que si su respuesta de error es de 32 bytes, necesita un int (que debería tener acceso atómico) y luego mantenga ese int en el estado apagado / falso / no listo hasta que se haya escrito su bloque de datos más grande y solo entonces escriba "verdadero" para activar el interruptor, por así decirlo.

Para el guardado correlacionado en el lado del cliente, debe simplemente conservar esos datos y no guardarlos hasta que obtenga el visto bueno del servidor o asegurarse de que tiene una opción de retroceso, digamos que una forma de eliminar es el servidor fallido.

Tenga en cuenta que nunca será 100% seguro a menos que realice un procedimiento de confirmación completo de 2 fases (guardar o eliminar del cliente puede fallar después de la señal del servidor) pero eso le costará 2 viajes al servidor como mínimo ( podría costarle 4 si su única opción de reversión es eliminar).

Idealmente, haría toda la versión de bloqueo de la operación en un hilo separado, pero necesitaría 4.0 para eso.

ZXX
fuente