Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi; Parte 1

August 23|5 Vistas|

Resumen: Si eres nuevo aquí, tal vez quieras suscribirte a mi feed RSS o seguirme en Twitter. ¡Gracias por su visita! Aprenda a sincronizar Core Data con un servicio web! Este es un post de iOS Tutorial miembro del equipo Chris Wagner, un entusiasta en la ing

Advertisement

Si eres nuevo aquí, tal vez quieras suscribirte a mi feed RSS o seguirme en Twitter. ¡Gracias por su visita!

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1

Aprenda a sincronizar Core Data con un servicio web!

Este es un post de iOS Tutorial miembro del equipo Chris Wagner, un entusiasta en la ingeniería de software siempre tratando de mantenerse por delante de la curva. También puedes encontrarlo en Google+.

Muchas aplicaciones que almacenan sus datos en una base de datos remota sólo funcionan cuando hay una conexión a Internet disponible. Piense en Twitter o Facebook - sin una conexión a Internet, no hacen mucho!

Pero es mucho más agradable (y más rápido) la experiencia del usuario si su aplicación puede funcionar incluso sin una conexión a Internet. Y hay buenas noticias - ¡puedes hacerlo con el almacenamiento en caché!

La idea es crear un caché local de sus datos para que pueda acceder a él si el usuario está en línea o sin conexión. Y cuando el usuario está en línea, sincroniza la caché y la base de datos remota.

En este tutorial, aprenderás cómo hacer exactamente eso. Usted creará una aplicación que almacena su información localmente con Core Data y sincronizará los registros con una base de datos remota cuando esté en línea. Puede utilizar las técnicas que aprenderá en este tutorial con cualquier servicio web, pero para mantener las cosas simples, utilizará una plataforma de servicio web popular llamada Parse para crear y alojar su servicio web.

Este tutorial supone que tiene familiaridad básica con los datos básicos y los servicios web. Si eres nuevo en estos temas, echa un vistazo a algunos de los otros tutoriales en este sitio.

Así que vamos a sincronizar ... 3, 2, 1, vamos!

Empezando

En este tutorial, va a crear una aplicación para ayudar a los usuarios a resolver un problema muy común: recordar fechas importantes. Si alguna vez ha olvidado el cumpleaños de un buen amigo o su aniversario, levante la mano. Sí, pensé que sí!

Para mantener el foco en el aspecto de sincronización, he creado un proyecto inicial para usted. Este proyecto inicial le llevará al punto de ejecutar la aplicación a nivel local, donde los usuarios pueden agregar y quitar cumpleaños y días festivos que se almacenarán localmente en Datos básicos.

Su primer orden de trabajo es descargar ejecutar el proyecto de arranque y familiarizarse con cómo funciona.

Echa un vistazo a la aplicación y asegúrate de comprender cómo funciona actualmente, cómo se configura la base de datos de datos básicos y cómo fluyen los controladores de vista juntos. Una vez más, si usted está confundido por cualquier cosa aquí usted puede ser que desee comprobar hacia fuera algunos de los otros tutoriales en este sitio primero.

Una vez que se sienta cómodo con la configuración de la aplicación, siga leyendo para aprender a sincronizar esta aplicación local a un servicio web remoto.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1
Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1
Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1
Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1

Configurar una cuenta Parse gratuita

Parse es un negocio que existe para proporcionar el back-end para sus aplicaciones, es decir, el nivel de datos de los servidores y el almacenamiento. Antes de que estos servicios existieran, con el fin de empezar con una aplicación conectada que tendría que configurar su propio servidor web, base de datos y probablemente escribir una gran cantidad de código. ¡Ya no!

Parse es gratis para una cantidad bastante sustancial de uso. El nivel gratuito proporciona 1.000.000 solicitudes de API y 1.000.000 de empujes por mes, y le proporciona 1 GB de almacenamiento de archivos. Todo eso y otros servicios incluidos son más que suficientes para obtener su aplicación establecida hasta el punto en el que probablemente generará ingresos más que suficientes para pagar por el servicio.

Este tutorial utiliza Parse por lo que tendrá que registrarse. Parse está expandiendo activamente su servicio y mejorando constantemente su sitio. El proceso de registro debe ser bastante sencillo para los nuevos usuarios. Vaya a hacer ese paso ahora antes de continuar con el resto del tutorial. ¿Ya eres un usuario de Parse? Entonces ¡adelante y entra!

Una vez que haya creado su cuenta y haya iniciado sesión, cree una nueva aplicación para este tutorial desde el panel de control. Tenga en cuenta que el nombre de su aplicación en Parse puede ser lo que quiera, pero se referenciará como CoreDataSyncTutorial, por lo que puede preferir usar ese nombre.

Una vez que haya iniciado sesión y haya creado su Parse App, probablemente encontrará que el navegador se encuentra en la pantalla de Parse con la pestaña Overview seleccionada. Seleccione la pestaña Explorador de datos. El explorador de datos mostrará todas sus clases (análogas a los objetos o entidades que vea al editar el esquema de datos básicos), pero hasta ahora no hay nada que ver. ¡El tiempo de conseguir ocupado y consigue el valor de su dinero fuera de este servicio libre!

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Presione el botón "+" para agregar una nueva clase.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Introduzca "Holiday" como el nombre. Parse admite algunos tipos de clases predefinidos, como "Usuario", y ofrece soporte para funciones comunes como iniciar sesión con nombre y contraseña para esas clases, pero por ahora la Personalización es la opción predeterminada y es la opción correcta para su aplicación.

Ahora agregue seis nuevas columnas con el botón "+ Col"; nombre, observedBy, imagen, wikipediaLink, los detalles y la fecha.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Presione el botón + Col para agregar una nueva columna

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Introduzca "name" como nombre y defina el tipo como String.

Sigue agregando las otras columnas con el botón + Col. Utilice la siguiente lista para ayudar en la creación de las columnas.

  • name: String
  • observedBy: Array
  • Imagen: Archivo
  • wikipediaLink: String
  • Detalles: String
  • Fecha: Fecha

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Sólo para salvar un poco de angustia más tarde, vuelva atrás y verifique que todas las columnas (y sus tipos correspondientes) se han agregado más tarde.

Repita el proceso anterior para otra clase llamada "Cumpleaños". Nuevamente, utilice + para crear la nueva clase como "Personalizada" y asígnele el nombre "Cumpleaños". Agregue las siguientes columnas a la nueva clase de cumpleaños:

  • name: String
  • Fecha: Fecha
  • facebook: String
  • giftIdeas: String
  • Imagen: Archivo

Añadir algunos datos

Hora de conseguir que todas las vacaciones importantes entró! Seleccione la clase "Vacaciones" y haga clic en el botón + Línea para agregar un nuevo registro.

Veamos; Hay día del pi, día del programador, día de la toalla, día de la charla como un pirata, día de la guerra de las galaxias, e innumerables otros días de fiesta increíblemente importantes - pero guardar cosas simples para el bien de este tutorial, nos aterramos a los ejemplos abajo;

Rellene los campos con los siguientes valores. Si el explorador de datos no le permite pulsar Volver para confirmar la entrada de datos en un campo, es probable que tenga algunos datos no válidos allí. Asegúrese de introducir los datos exactamente como se muestra, especialmente los formatos más difíciles como arrays.

  • observedBy: [ "US", "Reino Unido"]
  • Nombre: Navidad
  • Detalles: Dar regalos
  • Fecha: 2012-12-25T00: 00: 00.000Z
  • imagen: Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


    (Por fango1)

Cualquier campo que no se menciona anteriormente puede dejar en blanco, o introducir su propio valor, si se siente atrevido! Unesdoc.unesco.org unesdoc.unesco.org

Seleccione la clase Cumpleaños y haga clic en el botón + Línea para agregar un nuevo registro.

Si eres como el resto del mundo, probablemente siempre olvides los cumpleaños del elenco de Jersey Shore. Oye, le pasa a todo el mundo.

Rellene los campos con los siguientes valores:

  • Nombre: Nichole (Snooki) Polizzi
  • Fecha: 2012-11-01T00: 00: 00.000Z
  • facebook: NicoleSn00kiPolizzi
  • giftIdeas: un cerebro
  • imagen: Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Se recomienda encarecidamente agregar más registros, pero lo que ya ha introducido es suficiente para un conjunto de datos de ejemplo para el tutorial.

¡Bueno! Ahora has creado algún contenido para tu aplicación, pero ahora necesitas introducir esos datos en tu aplicación. La siguiente sección le muestra cómo hacerlo!

Escribir un cliente AFNetworking para hablar con la API Parse REST

AFNetworking es una clase desarrollada por Matt Thompson y Scott Raymond y es descrita como "una deliciosa biblioteca de redes para iOS y Mac OS X." Hace tareas comunes como las solicitudes http asíncronas mucho más fáciles. A partir de este momento, AFNetworking no utiliza ARC, por lo que si lo agrega manualmente a un proyecto que utiliza ARC, el indicador de compilador -fno-objc-arc es necesario en todos sus archivos. Esto ya se ha hecho para usted en el proyecto tutorial.

Nota: Este tutorial asume cierta experiencia AFNetworking. Si no ha utilizado esta biblioteca antes, debería tener una lectura más de Introducción a AFNetworking antes de ir más lejos en este tutorial.

El primer paso para utilizar AFNetworking en su aplicación es crear un cliente que utilice el patrón Singleton. Vaya a File \ New \ File ..., elija iOS \ Cocoa Touch \ Objective-C clase y haga clic en Siguiente. Escriba AFHTTPClient para Subclase de, nombre de la nueva clase SDAFParseAPIClient, haga clic en Siguiente y Crear.

Abra su archivo de interfaz, SFAFParseAPIClient.h y agregue un nuevo método de clase:

  #import "AFHTTPClient.h" @interface SDAFParseAPIClient: AFHTTPClient + (SDAFParseAPIClient *) sharedClient;  @fin 

Y luego complete la implementación en SFAFParseAPIClient.m:

  #import "SDAFParseAPIClient.h" static NSString * const kSDFParseAPIBaseURLString = @ "https://api.parse.com/1/";  Static NSString * const kSDFParseAPIApplicationId = @ "SU_APPLICACIÓN_ID";  Static NSString * const kSDFParseAPIKey = @ "SU_REST_API_KEY";  @implementation SDAFParseAPIClient + (SDAFParseAPIClient *) sharedClient {static SDAFParseAPIClient * sharedClient = nil;  Static dispatch_once_t onceToken;  Dispatch_once (& onceToken, ^ {sharedClient = [[SDAFParseAPIClient alloc] initWithBaseURL: [NSURL URLWithString: kSDFParseAPIBaseURLString]];));  Return sharedClient;  }   @fin 

Tenga en cuenta que en el código anterior, debe insertar la información personal generada por Parse: YOUR_APPLICATION_ID y YOUR_REST_API_KEY son exclusivos de su aplicación. Sustituya YOUR_APPLICATION_ID y YOUR_REST_API_KEY por los valores de la pestaña Resumen de la ventana Parse project.

También crea tres variables NSString estáticas para la URL de la API de Parse, su Id de la API de Parse API y su Parse API Key e implementa el método + sharedClient que utiliza GCD para crear una nueva instancia de la clase y almacenar su referencia en una variable estática , Convirtiéndose así en un Singleton.

Siguiente importación AFJSONRequestOperation.h en SDAFParseAPIClient.m:

  #import "AFJSONRequestOperation.h" 

A continuación, reemplace -initWithBaseURL: en SDAFParseAPIClient.m para establecer la codificación de parámetros en JSON e inicialice los encabezados predeterminados para incluir su Parse Application ID y Parse API Key:

  - (id) initWithBaseURL: (NSURL *) url {self = [super initWithBaseURL: url];  If (self) {[self registerHTTPOperationClass: [clase AFJSONRequestOperation]];  [Self setParameterEncoding: AFJSONParameterEncoding];  [Self setDefaultHeader: @ "Valor de aplicación de X-Parse" valor: kSDFParseAPIApplicationId];  [Auto setDefaultHeader: @ "X-Parse-REST-API-Key" valor: kSDFParseAPIKey];  } Return self;  } 

Agregue dos métodos a su interfaz para SDAFParseAPIClient en SDAFParseAPIClient.h:

  - (NSMutableURLRequest *) GETRequestForClass: (NSString *) className parámetros: (NSDictionary *) parámetros;  - (NSMutableURLRequest *) GETRequestForAllRecordsOfClass: (NSString *) className updatedAfterDate: (NSDate *) updatedDate; 

Beneath -initWithBaseURL en SDAFParseAPIClient.m, implemente los dos métodos -GETRequestForClass: parameters: y -GETRequestForAllRecordsOfClass: updatedAfterDate :.

  - (NSMutableURLRequest *) GETRequestForClass: (NSString *) className parámetros: (NSDictionary *) parámetros {NSMutableURLRequest * request = nil;  Request = [self requestWithMethod: @ ruta "GET": [NSString stringWithFormat: @ "classes /% @", className] parámetros: parámetros];  solicitud de devolución;  } - (NSMutableURLRequest *) GETRequestForAllRecordsOfClass: (NSString *) className updatedAfterDate: (NSDate *) updatedDate {NSMutableURLRequest * request = nil;  NSDictionary * parameters = nil;  If (actualizaciónDate) {NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];  [DateFormatter setDateFormat: @ "aaaa-MM-dd'T'HH: mm: ss.'999Z '"];  [DateFormatter setTimeZone: [NSTimeZone timeZoneWithName: @ "GMT"]];  NSString * jsonString = [NSString stringWithFormat: @ "{\" actualizaciónAt \ ": {\" $ gte \ ": {\" __ type \ ": \" Fecha \ ", \" iso \ ": \"% @ \ " }}} ", [DateFormatter stringFromDate: updatedDate]];  Parameters = [NSDictionary dictionaryWithObject: jsonString forKey: @ "donde"];  } Request = [self GETRequestForClass: className parámetros: parámetros];  solicitud de devolución;  } 

-GETRequestForClass: parameters: devolverá un NSMutableURLRequest utilizado para obtener registros de la Parse API para un Parse Object con el nombre de clase 'className' y enviar un NSDictionary de parámetros. Los parámetros aceptables se pueden ver en la documentación de la API Parse REST.

-GETRequestForAllRecordsOfClass: updatedAfterDate: devolverá un NSMutableURLRequest utilizado para obtener registros de la Parse API que se actualizaron después de un NSDate especificado. Observe que este método crea el diccionario de parámetros y llama a su otro método. Esto es simplemente un método de conveniencia para que el diccionario de parámetros no tenga que ser generado cada vez que se realiza una solicitud con una fecha.

¡Bueno! Así que ahora tiene un cliente de AFNetworking listo para funcionar. Pero no es mucho bueno hasta que obtenga los datos sincronizados! La sección siguiente le llevará allí.

Crear una clase Singleton "Sync Engine" para manejar la sincronización

Agregue otra clase Singleton nueva para administrar todas las rutinas de sincronización entre Core Data y su servicio remoto (Parse). Vaya a File \ New \ File ..., elija iOS \ Cocoa Touch \ Objective-C clase y haga clic en Siguiente. Introduzca NSObject para Subclase de, nombre de la nueva clase SDSyncEngine, haga clic en Siguiente y Crear.

  #import <Foundation / Foundation.h> @interface SDSyncEngine: NSObject + (SDSyncEngine *) sharedEngine;  @fin 

Agregue un método estático + sharedEngine para acceder a la instancia de Singleton.

  #import "SDSyncEngine.h" @implementación SDSyncEngine + (SDSyncEngine *) sharedEngine {static SDSyncEngine * sharedEngine = nil;  Static dispatch_once_t onceToken;  Dispatch_once (& onceToken, ^ {sharedEngine = [[SDSyncEngine alloc] init];});  Return sharedEngine;  }   @fin 

Para sincronizar datos entre Core Data (sus registros locales) y Parse (los registros del lado del servidor), utilizará una estrategia en la que las subclases NSManagedObject están registradas con SDSyncEngine. El motor de sincronización entonces manejará el proceso necesario para tomar los datos de Parse, y ... uh ... analizarlo (por falta de un término mejor!), Y guardarlo en Core Data.

Declare un nuevo método en SDSyncEngine.h para registrar clases con el motor de sincronización:

  - (void) registerNSManagedObjectClassToSync: (Clase) aClass; 

Agregue una categoría "privada" en SDSyncEngine.m con una propiedad para almacenar todas las clases registradas y sintetizar:

  @interface SDSyncEngine () @property (no atómico, fuerte) NSMutableArray * registeredClassesToSync;  @end @implementation SDSyncEngine @synthesize registeredClassesToSync = _registeredClassesToSync;  ... 

Beneath + sharedEngine, agregue la implementación:

  - (void) registerNSManagedObjectClassToSync: (Clase) aClass {if (! Self.registeredClassesToSync) {self.registeredClassesToSync = [matriz NSMutableArray];  } If ([aClass isSubclassOfClass: [Clase NSManagedObject]]) {if (! [Self.registeredClassesToSync containsObject: NSStringFromClass (aClass)]) {[self.registeredClassesToSync addObject: NSStringFromClass (aClass)];  } Else {NSLog (@ "No se puede registrar% @ ya que ya está registrado", NSStringFromClass (aClass));  }} Else {NSLog (@ "No se puede registrar% @ como no es una subclase de NSManagedObject", NSStringFromClass (aClass));  }} 

Este método toma una clase, inicializa la propiedad registeredClassesToSync (si no lo está), verifica que el objeto es una subclase de NSManagedObject y, de ser así, lo agrega a la matriz registeredClassesToSync.

Nota: Siempre es preferible escribir código eficiente, pero cuando se trata de la sincronización de datos con un servicio en línea, que desea obtener el "retorno de la inversión" más con cada llamada de sincronización. El servicio de Parse puede tener un nivel gratuito, pero eso no significa que sea ilimitado - y que desea hacer uso de cada llamada de la manera más eficiente posible! Además, tenga en cuenta que cada pieza de datos extraída de la red móvil cuenta con el plan de datos del usuario. Nadie querrá utilizar su aplicación si se va a acumular cargos de datos en exceso! Unesdoc.unesco.org unesdoc.unesco.org

Una de las principales preocupaciones de mantener los datos sincronizados es hacerlo eficientemente, por lo que no tiene sentido descargar y procesar cada registro cada vez que se ejecuta el proceso de sincronización. Una solución es usar un proceso generalmente conocido como realizar una "sincronización delta", que significa "sólo me dan las cosas nuevas, no me importa lo que ya sé".

Su proceso de sincronización delta se realizará mirando el atributo "updatedAt" en sus Entidades y determinando cuál es la más reciente. Esta fecha se utilizará para pedir al servicio remoto que sólo devuelva registros que fueron modificados después de esta fecha.

Importar SDCoreDataController.h en SDSyncEngine.m:

  #import "SDCoreDataController.h" 

A continuación, agregue este nuevo método:

  - (NSDate *) mostRecentUpdatedAtDateForEntityWithName: (NSString *) entityName {__block NSDate * date = nil;  // // Crear una nueva solicitud de búsqueda para la entidad especificada // NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName: entityName];  // // Establece los descriptores de orden en la solicitud para ordenar por updatedAt en orden descendente // [request setSortDescriptors: [NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey: @ "updatedAt" ascendente: NO]]];  // // Sólo le interesa 1 resultado, por lo que limitar la petición a 1 // [request setFetchLimit: 1];  [[[SDCoreDataController sharedInstance] backgroundManagedObjectContext] performBlockAndWait: ^ {NSError * error = nil;  NSArray * results = [[[SDCoreDataController sharedInstance] backgroundManagedObjectContext] executeFetchRequest: error de solicitud: & error];  If ([results lastObject]) {// // Establece la fecha en el resultado obtenido // date = [[results lastObject] valueForKey: @ "updatedAt"];  }}];  Fecha de regreso;  } 

Esto devuelve la "fecha de última modificación más reciente" para una entidad específica.

A continuación, agregue otro método nuevo downloadDataForRegisteredObjects: below mostRecentUpdatedAtDateForEntityWithName:

  #import "SDAFParseAPIClient.h" #import "AFHTTPRequestOperation.h" 

En SDSyncEngine.h import SDAFParseAPIClient.h y AFHTTPRequestOperation.h.

  - (void) downloadDataForRegisteredObjects: (BOOL) useUpdatedAtDate {NSMutableArray * operaciones = [matriz NSMutableArray];  Para (NSString * className en self.registeredClassesToSync) {NSDate * mostRecentUpdatedDate = nil;  If (useUpdatedAtDate) {mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName: className];  } NSMutableURLRequest * request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass: className updatedAfterDate: mostRecentUpdatedDate];  AFHTTPRequestOperation * operación = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest: solicitud de éxito: ^ (AFHTTPRequestOperation * operación, id responseObject) {if ([responseObject esKindOfClass: [NSDictionary class]]) {NSLog (@ "Response for% @:% @ ClassName, responseObject);  // 1 // Necesidad de escribir archivos JSON en disco}} falla: ^ (operación AFHTTPRequestOperation *, error NSError *) {NSLog (@ "Petición de clase% @ ha fallado con error:% @", className, error);  }];  [Operaciones addObject: operación];  } [[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations: operaciones progressBlock: ^ (NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {} completionBlock: ^ (operaciones NSArray *) NSLog (@ "Todas las operaciones completadas");  // 2 // Necesidad de procesar registros JSON en datos básicos}];  } 

Este método itera sobre cada clase registrada, crea NSMutableURLRequests para cada uno, utiliza esas solicitudes para crear AFHTTPRequestOperations y, finalmente, finalmente pasa esas operaciones al método -enqueueBatchOfHTTPRequestOperations: progressBlock: completionBlock de SDAFParseAPIClient.

Tenga en cuenta que este método no está completo! Eche un vistazo al comentario 1 dentro del bloque de éxito para la AFHTTPRequestOperation. A continuación, agregará un método en este bloque que toma la respuesta recibida del servicio remoto y la guarda en disco. Ahora echa un vistazo al comentario 2; Este bloque será llamado cuando todas las operaciones hayan terminado. Posteriormente agregará un método aquí que toma las respuestas guardadas en el disco y las procesa en Datos básicos.

¡Increíble! Has escrito una tonelada de código ya! Estás casi al punto de empezar a ver algún progreso en la aplicación. Sin embargo, en este punto le falta una manera de iniciar todo el proceso de sincronización - que es el punto de esta aplicación! :] Pero pise con cuidado - la forma en que el proceso de sincronización se inicia es crucial, y es importante realizar un seguimiento del estado, ya que no desea iniciar el proceso de sincronización más de una vez.

Agrega una propiedad de sólo lectura a SDSyncEngine.h para realizar un seguimiento del estado de sincronización:

  @property (atómico, sólo lectura) BOOL syncInProgress; 

Sintetizar la propiedad syncInProgress:

  @synthesize syncInProgress = _syncInProgress; 

Declare un método -startSync en SDSyncEngine.h:

  - (void) startSync; 

Y añadir su implementación en SDSyncEngine.m:

  - (void) startSync {if (! Self.syncInProgress) {[self willChangeValueForKey: @ "syncInProgress"];  _syncInProgress = SI;  [Self didChangeValueForKey: @ "syncInProgress"];  Dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {[self downloadDataForRegisteredObjects: YES];});  }} 

Ha implementado el método -startSync que comprueba primero si la sincronización ya está en curso y, si no, establece la propiedad syncInProgress. A continuación, utiliza GCD para iniciar un bloque asíncrono que llama a su método downloadDataForRegisteredObjects :.

Moviéndose a lo largo, necesita registrar sus clases de NSManagedObject e iniciar la sincronización Importación de las clases apropiadas en SDAppDelegate.m:

  #import "SDSyncEngine.h" #import "Holiday.h" #import "Birthday.h" 

A continuación, agregue esto a la aplicación: didFinishLaunchingWithOptions:

  - (BOOL) aplicación: (UIApplication *) aplicación didFinishLaunchingWithOptions: (NSDictionary *) launchOptions {[[SDSyncEngine sharedEngine] registerNSManagedObjectClassToSync: [Clase de vacaciones]];  [[SDSyncEngine sharedEngine] registerNSManagedObjectClassToSync: [Clase de cumpleaños]];  Devolver SI;  } 

Esto registra las clases Holiday y Birthday con el motor de sincronización en -application: didFinishLaunchingWithOptions :; Este patrón le permite agregar fácilmente otros objetos al motor de sincronización en el futuro, si desea extender la aplicación. Escalabilidad es siempre bueno! Unesdoc.unesco.org unesdoc.unesco.org

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Luego llama a startSync en applicationDidBecomeActive:

  - (void) applicationDidBecomeActive: (UIApplication *) aplicación {[[SDSyncEngine sharedEngine] startSync];  } 

¡Lo hiciste! Usted ha sido un buen soldado llegar a este punto - y sí, usted puede construir y ejecutar la aplicación! En tu consola Xcode o dispositivo verás algo muy cercano a lo siguiente:

  2012-07-09 00: 39: 15.764 SignificantDates [70812: fb03] Respuesta de vacaciones: {results = ({createdAt = "2012-07-09T07: 13: 24.593Z"; date = {"__type" = Date; iso = "2012-12-25T00: 00: 00.000Z";}; details = "Dar regalos"; image = {"__type" = Archivo; name = "9d2d8a0d-36fb-4abe-9908-bebd7fb39056-christmas.gif"; Url = "http://files.parse.com/bcee5dd3-46dc-40a8-abe6-37da2732e809/9d2d8a0d-36fb-4abe-9908-bebd7fb39056-christmas.gif";}; name = Christmas; objectId = FVkYM9QROH; observedBy = (EE.UU., Reino Unido); updatedAt = "2012-07-09T07: 36: 28.097Z";});  } 2012-07-09 00: 39: 15.765 SignificantDates [70812: fb03] Respuesta para cumpleaños: {results = ({createdAt = "2012-07-09T07: 34: 39.745Z"; date = {"__type" = Date; = "Un cerebro"; image = {"__type" = Archivo; name = "5dcc3de5-3add-466a-bb46-31a7b7115903- Nicole-polizzi.jpg "; url =" http://files.parse.com/bcee5dd3-46dc-40a8-abe6-37da2732e809/5dcc3de5-3add-466a-bb46-31a7b7115903-nicole-polizzi.jpg ";}; name = "Nichole (Snooki) Polizzi"; objectId = 23S04NSPOR; updatedAt = "2012-07-09T07: 36: 11.792Z";});  } 2012-07-09 00: 39: 15.767 DATOS IMPORTANTES [70812: fb03] Todas las operaciones finalizadas 

¡Algo muy emocionante! Ahora es el momento de hacer algo con estos datos; Sólo está flotando alrededor, y es necesario persistir estos datos en el almacenamiento local. El concepto clave en las transacciones de datos es realizar tantas operaciones de red como sea posible en un solo lote, con el fin de reducir el tráfico de red. Una manera realmente fácil de lograr esto es poner en cola todas las solicitudes y guardar las respuestas en disco antes de procesarlas.

Agregue estos tres tres métodos a SDSyncEngine.m (debajo de downloadDataForRegisteredObjects :) para manejar la administración de archivos:

  #pragma mark - Administración de archivos (NSURL *) applicationCacheDirectory {return [[[NSFileManager defaultManager] URLsForDirectory: NSCachesDirectory inDomains: NSUserDomainMask] lastObject];  } - (NSURL *) JSONDataRecordsDirectory {NSFileManager * fileManager = [NSFileManager defaultManager];  NSURL * url = [NSURL URLWithString: @ "JSONRecords /" relativeToURL: [autoinserciónCacheDirectory]];  NSError * error = nil;  If (! [FileManager fileExistsAtPath: [ruta de url]]) {[fileManager createDirectoryAtPath: [ruta de acceso de url] withIntermediateDirectories: SÍ atributos: nil error: & error];  } Url de retorno;  } - (void) writeJSONResponse: (id) respuesta toDiskForClassWithName: (NSString *) className {NSURL * fileURL = [NSURL URLWithString: className relativoToURL: [auto JSONDataRecordsDirectory]];  If (! NSDictionary *) writeToFile: [fileURL path] atómicamente: YES]) {NSLog (@ "Error al guardar la respuesta al disco, intentará eliminar valores NSNull y volver a intentarlo.");  // quita NSNulls y vuelve a intentarlo ... NSArray * records = [response objectForKey: @ "results"];  NSMutableArray * nullFreeRecords = [matriz NSMutableArray];  Para (registro NSDictionary * en registros) {NSMutableDictionary * nullFreeRecord = [NSMutableDictionary dictionaryWithDictionary: registro];  [Record enumerateKeysAndObjectsUsingBlock: ^ (clave id, id obj, BOOL * stop) {if ([obj isKindOfClass: [NSNull clase]]) {[nullFreeRecord setValue: nil forKey: key];  }}];  [NullFreeRecords addObject: nullFreeRecord];  } NSDictionary * nullFreeDictionary = [NSDictionary dictionaryWithObject: nullFreeRecords forKey: @ "results"];  If (! [NullFreeDictionary writeToFile: [fileURL path] atómicamente: YES]) {NSLog (@ "Falló todos los intentos de guardar la respuesta en el disco:% @", respuesta);  }}} 

Los dos primeros métodos devuelven un NSURL a una ubicación en el disco donde residirán los archivos. El tercero es más específico para la aplicación y el servicio remoto; Cada respuesta se guarda en disco como su respectivo nombre de clase.

Una situación interesante con el API Parse es que devolverá valores, que se traducen a objetos NSNull cuando se convierten a NSDictionary. Desafortunadamente, no es posible serializar un objeto NSNull al disco - no se puede guardar lo que es, esencialmente, nada! Por lo tanto, debe quitar todos los objetos NSNull antes de persistir los datos.

Usted tendrá un enfoque optimista aquí cuando persiste sus datos. Intentará igualar la respuesta primero, sin escanear objetos NSNull. Si ese intento falla, escanea los objetos NSNull, elimina los que se encuentren y vuelve a intentarlo. Si ese intento falla, entonces todo lo que puede hacer es recurrir a las técnicas de manejo de errores estándar, donde se alerta al usuario o se informa del problema. Este tutorial no cubre las operaciones de manejo de errores, pero puede agregar fácilmente las suyas si así lo desea.

A continuación es el momento de modificar downloadDataForRegisteredObjects - reemplace el comentario de marcador de posición 1 como sigue:

  ... - (void) downloadDataForRegisteredObjects: (BOOL) useUpdatedAtDate {NSMutableArray * operaciones = [matriz NSMutableArray];  Para (NSString * className en self.registeredClassesToSync) {NSDate * mostRecentUpdatedDate = nil;  If (useUpdatedAtDate) {mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName: className];  } NSMutableURLRequest * request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass: className updatedAfterDate: mostRecentUpdatedDate];  AFHTTPRequestOperation * operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest: solicitud de éxito: ^ (AFHTTPRequestOperation * operación, id responseObject) {if ([responseObject esKindOfClass: [NSDictionary class]]) {[self writeJSONResponse: responseObject toDiskForClassWithName: className];  }} Fall: ^ (operación AFHTTPRequestOperation *, error NSError *) {NSLog (@ "La solicitud de clase% @ ha fallado con error:% @", className, error);  }];  [Operaciones addObject: operación];  } [[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations: operaciones progressBlock: ^ (NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {} completionBlock: ^ (operaciones NSArray *) NSLog (@ "Todas las operaciones completadas");  // 2 // Necesidad de procesar registros JSON en datos básicos}];  } 

Has reemplazado el comentario con una acción mucho más útil: escribir los datos en un archivo. El NSLog desaparece y se llama al método -writeJSONResponse: toDiskForClassWithName:.

¡Crea y ejecuta tu aplicación! Verá que los archivos se guardan en el disco.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Si desea ver los resultados de todo su trabajo duro, puede ver los archivos reales abriendo Finder, abra el menú Ir, elija "Ir a la carpeta ..." y escriba '~ / Biblioteca / Soporte de aplicaciones / Simulador de iPhone /'. (Si prefiere hacer clic en todas las carpetas, mantenga presionada la tecla "opción" al seleccionar Ir al Finder y la Biblioteca volverá a aparecer en la lista de destinos posibles.) Desde aquí, tendrá que hacer algún trabajo de detective!

Primero, determine la versión del simulador que está ejecutando. Abra la carpeta adecuada para ese simulador y, a continuación, abra Aplicaciones. A continuación, es probable que vea un número de carpetas con nombres aleatorios. ¡Eek! Sigue así. Una manera fácil de encontrar la carpeta correcta es ordenar las carpetas por Date Modified y abrir la carpeta modificada más recientemente. Usted sabrá que encontró la carpeta correcta una vez que vea "SignificantDates.app" en la carpeta.

Una vez que se encuentre en la carpeta de la aplicación correcta, abra la biblioteca> Caches> JSONRecords. Aquí verá los archivos de vacaciones y cumpleaños. Puede abrir los archivos con Xcode para verlos, ya que son simples archivos de lista de propiedades. ¡Hooray! Si ve sus datos en los archivos, entonces sabe que su aplicación está funcionando. Unesdoc.unesco.org unesdoc.unesco.org

En este punto el proceso de sincronización está terminado! A pesar de que no hace un montón en este momento, todavía debe tener en cuenta cuando la sincronización está en curso, y cuando no lo es. Ya tienes un BOOL para realizar un seguimiento de esto, pero debes configurarlo en NO en este punto para detener la sincronización. También querrá saber cuándo se sincroniza la aplicación por primera vez, también conocida como sincronización inicial. Para realizar el seguimiento de esta información, añada la siguiente @interfaz SDSyncEngine () en SDSyncEngine.m:

  NSString * const kSDSyncEngineInitialCompleteKey = @ "SDSyncEngineInitialSyncCompleted";  NSString * const kSDSyncEngineSyncCompletedNotificationName = @ "SDSyncEngineSyncCompleted"; 

A continuación, agregue estos métodos arriba -mostRecentUpdatedAtDateForEntityWithName:

  - (BOOL) initialSyncComplete {return [[[NSUserDefaults estándarUserDefaults] valueForKey: kSDSyncEngineInitialCompleteKey] boolValue];  } - (void) setInitialSyncCompleted {[[NSUserDefaults standardUserDefaults] setValue: [NSNumber numberWithBool: YES] forKey: kSDSyncEngineInitialCompleteKey];  [[NSUserDefaults standardUserDefaults] sincronizar];  } - (void) executeSyncCompletedOperations {dispatch_async (dispatch_get_main_queue (), ^ {[self setInitialSyncCompleted]; [[NSNotificationCenter defaultCenter] postNotificationName: kSDSyncEngineSyncCompletedNotificationName objeto: nil]; [self willChangeValueForKey: @ "syncInProgress" : @ "SyncInProgress"];});  } 

Después de -startSync agregue el método anterior al que se llamará cuando finalice el proceso de sincronización.

Procesar datos de servicio remoto en Datos básicos

Bien, usted tiene nuestros datos que persisten al disco en un formato de la lista de la característica. Pero lo que realmente quieres hacer es procesarlo en Core Data. Aquí es donde usted va a hacer un poco de trabajo pesado y entrar en los detalles nitty arenoso. Esta es la parte donde la verdadera magia sucede!

To start off you will first need a way to retrieve the files from disk. Add -JSONDictionaryForClassWithName: in SDSyncEngine.m:

 - (NSDictionary *)JSONDictionaryForClassWithName:(NSString *)className { NSURL *fileURL = [NSURL URLWithString:className relativeToURL:[self JSONDataRecordsDirectory]]; return [NSDictionary dictionaryWithContentsOfURL:fileURL];  } 

One caveat to the NSDictionary that -JSONDictionaryForClassWithName: returns is that the information you are interested in is will be in an NSArray with the key “results”. So to make things easier for processing purposes, add another method to access the data in the NSArray and spice it up a little to allow for sorting of the records by a specified key.

Add this beneath -JSONDictionaryForClassWithName: in SDSyncEngine.m:

 - (NSArray *)JSONDataRecordsForClass:(NSString *)className sortedByKey:(NSString *)key { NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className]; NSArray *records = [JSONDictionary objectForKey:@"results"]; return [records sortedArrayUsingDescriptors:[NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey:key ascending:YES]]];  } 

This method calls the previous method you implemented, and returns an NSArray of all the records in the response, sorted by the specified key.

You won't really need the JSON responses that were saved to disk much past this point, so add another method to delete them when you're finished with them. Add the following method above -JSONDictionaryForClassWithName:

 - (void)deleteJSONDataRecordsForClassWithName:(NSString *)className { NSURL *url = [NSURL URLWithString:className relativeToURL:[self JSONDataRecordsDirectory]]; NSError *error = nil; BOOL deleted = [[NSFileManager defaultManager] removeItemAtURL:url error:&error]; if (!deleted) { NSLog(@"Unable to delete JSON Records at %@, reason: %@", url, error);  }} 

In order to translate records from JSON to NSManagedObjects, you will need a few methods. First, you will need to translate the JSON values to Objective-C properties; the method you use will vary based on the remote service you are working with. In this case, you are using Parse which has a few “special” data types. The data you'll be concerned with here are Files and Dates. Files are returned as URLs to the file's location, and Dates are returned in the following format:

 { "__type": "Date", "iso": "2011-08-21T18:02:52.249Z" } 

Since the date is in the format of a string, you will want some methods to convert from a Parse formatted date string to an NSDate and back to an NSString. NSDateFormatter can help with this, but they are very expensive to allocate — so first add a new NSDateFormatter property that you can re-use.

Add the dateFormatter property in your private category:

 @interface SDSyncEngine ()   @property (nonatomic, strong) NSMutableArray *registeredClassesToSync; @property (nonatomic, strong) NSDateFormatter *dateFormatter;  @fin 

Don't forget to synthesize it as well in your @implementation:

 @synthesize dateFormatter = _dateFormatter; 

And add these three methods above the #pragma mark -File Management.

 - (void)initializeDateFormatter { if (!self.dateFormatter) { self.dateFormatter = [[NSDateFormatter alloc] init]; [self.dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; [self.dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]]; } }   - (NSDate *)dateUsingStringFromAPI:(NSString *)dateString { [self initializeDateFormatter]; // NSDateFormatter does not like ISO 8601 so strip the milliseconds and timezone dateString = [dateString substringWithRange:NSMakeRange(0, [dateString length]-5)]; return [self.dateFormatter dateFromString:dateString]; }   - (NSString *)dateStringForAPIUsingDate:(NSDate *)date { [self initializeDateFormatter]; NSString *dateString = [self.dateFormatter stringFromDate:date]; // remove Z dateString = [dateString substringWithRange:NSMakeRange(0, [dateString length]-1)]; // add milliseconds and put Z back on dateString = [dateString stringByAppendingFormat:@".000Z"]; return dateString;  } 

The first method -initializeDateFormatter will initialize your dateFormatter property. The second method -dateUsingStringFromAPI: receives an NSString and returns an NSDate object. The third method -dateStringForAPIUsingDate: receives an NSDate and returns an NSString.

Take a little closer look, there, detective — the second and third methods do something a little strange. Parse uses timestamps in the ISO 8601 format which do not translate to NSDate objects very well, so you need to do some stripping and appending of the milliseconds and Z flag (used to denote the timezone). (Oh standards…there are so many wonderful ones to choose from!) :]

Next add this method below mostRecentUpdatedAtDateForEntityWithName:

 - (void)setValue:(id)value forKey:(NSString *)key forManagedObject:(NSManagedObject *)managedObject { if ([key isEqualToString:@"createdAt"] || [key isEqualToString:@"updatedAt"]) { NSDate *date = [self dateUsingStringFromAPI:value]; [managedObject setValue:date forKey:key]; } else if ([value isKindOfClass:[NSDictionary class]]) { if ([value objectForKey:@"__type"]) { NSString *dataType = [value objectForKey:@"__type"]; if ([dataType isEqualToString:@"Date"]) { NSString *dateString = [value objectForKey:@"iso"]; NSDate *date = [self dateUsingStringFromAPI:dateString]; [managedObject setValue:date forKey:key]; } else if ([dataType isEqualToString:@"File"]) { NSString *urlString = [value objectForKey:@"url"]; NSURL *url = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLResponse *response = nil; NSError *error = nil; NSData *dataResponse = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; [managedObject setValue:dataResponse forKey:key]; } else { NSLog(@"Unknown Data Type Received"); [managedObject setValue:nil forKey:key]; } } } else { [managedObject setValue:value forKey:key];  }} 

This method accepts a value, key, and managedObject. If the key is equal to createdDate or updatedAt, you will be converting them to NSDates. If the key is an NSDictionary you will check the __type key to determine the data type Parse returned. If it is a Date, you will convert the value from an NSString to an NSDate. If it is a File, you will do a little more work since you are interested in getting the image itself!

To get the image, send off a request to download the image file. It is important to note that downloading the image data can take a considerable amount of time, so this may only work efficiently with smaller data sets. Another solution would be to fetch the image data when the record is accessed (lazy loading), but it would only be available if the user has an Internet connection at the time of lazy loading.

If the data type is anything other than a File or Date there is no way to know what to do with it so set the value to nil. In any other case you will simply pass the value and key through untouched and set them on the managedObject.

Next, add methods that create an NSManagedObject or update an NSManagedObject based on a record from the JSON response to SDSyncEngine.h:

 typedef enum { SDObjectSynced = 0, SDObjectCreated, SDObjectDeleted, } SDObjectSyncStatus; 

Then add these two methods right above setValue:forKey:forManagedObject:

 - (void)newManagedObjectWithClassName:(NSString *)className forRecord:(NSDictionary *)record { NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:[[SDCoreDataController sharedInstance] backgroundManagedObjectContext]]; [record enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [self setValue:obj forKey:key forManagedObject:newManagedObject]; }]; [record setValue:[NSNumber numberWithInt:SDObjectSynced] forKey:@"syncStatus"]; }   - (void)updateManagedObject:(NSManagedObject *)managedObject withRecord:(NSDictionary *)record { [record enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [self setValue:obj forKey:key forManagedObject:managedObject]; }];  } 
  • newManagedObjectWithClassName:forRecord: accepts a className and a record, using this information it will create a new NSManagedObject in the backgroundManagedObjectContext
  • -updateManagedObject:withRecord: accepts an NSManagedObject and a record, using this information it will update the passed NSManagedObject with the record information in the backgroundManagedObjectContext

You're heading into the home stretch! Just two more methods before you tie it all together in a method that actually processes the JSON data into Core Data. Add these methods right after -setValue:forKey:forManagedObject:

 - (NSArray *)managedObjectsForClass:(NSString *)className withSyncStatus:(SDObjectSyncStatus)syncStatus { __block NSArray *results = nil; NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"syncStatus = %d", syncStatus]; [fetchRequest setPredicate:predicate]; [managedObjectContext performBlockAndWait:^{ NSError *error = nil; results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; }]; return results; }   - (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds { __block NSArray *results = nil; NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className]; NSPredicate *predicate; if (inIds) { predicate = [NSPredicate predicateWithFormat:@"objectId IN %@", idArray]; } else { predicate = [NSPredicate predicateWithFormat:@"NOT (objectId IN %@)", idArray]; }   [fetchRequest setPredicate:predicate]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey:@"objectId" ascending:YES]]]; [managedObjectContext performBlockAndWait:^{ NSError *error = nil; results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; }]; return results;  } 
  • -managedObjectsForClass:withSyncStatus: returns an NSArray of NSManagedObjects for the specified className where their syncStatus is set to the specified status,
  • -managedObjectsForClass:sortedByKey:usingArrayOfIds:inArrayOfIds: returns an NSArray of NSManagedObjects for the specified className, sorted by key, using an array of objectIds, and you can tell the method to return NSManagedObjects whose objectIds match those in the passed array or those who do not match those in the array. You'll read more about this method later on.

Now to put all of these methods to use!

 - (void)processJSONDataRecordsIntoCoreData { NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext]; // // Iterate over all registered classes to sync // for (NSString *className in self.registeredClassesToSync) { if (![self initialSyncComplete]) { // import all downloaded data to Core Data for initial sync // // If this is the initial sync then the logic is pretty simple, you will fetch the JSON data from disk // for the class of the current iteration and create new NSManagedObjects for each record // NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className]; NSArray *records = [JSONDictionary objectForKey:@"results"]; for (NSDictionary *record in records) { [self newManagedObjectWithClassName:className forRecord:record]; } } else { // // Otherwise you need to do some more logic to determine if the record is new or has been updated. // First get the downloaded records from the JSON response, verify there is at least one object in // the data, and then fetch all records stored in Core Data whose objectId matches those from the JSON response. // NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:@"objectId"]; if ([downloadedRecords lastObject]) { // // Now you have a set of objects from the remote service and all of the matching objects // (based on objectId) from your Core Data store. Iterate over all of the downloaded records // from the remote service. // NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:@"objectId" usingArrayOfIds:[downloadedRecords valueForKey:@"objectId"] inArrayOfIds:YES]; int currentIndex = 0; // // If the number of records in your Core Data store is less than the currentIndex, you know that // you have a potential match between the downloaded records and stored records because you sorted // both lists by objectId, this means that an update has come in from the remote service // for (NSDictionary *record in downloadedRecords) { NSManagedObject *storedManagedObject = nil; // Make sure we don't access an index that is out of bounds as we are iterating over both collections together if ([storedRecords count] > currentIndex) { storedManagedObject = [storedRecords objectAtIndex:currentIndex]; }   if ([[storedManagedObject valueForKey:@"objectId"] isEqualToString:[record valueForKey:@"objectId"]]) { // // Do a quick spot check to validate the objectIds in fact do match, if they do update the stored // object with the values received from the remote service // [self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record]; } else { // // Otherwise you have a new object coming in from your remote service so create a new // NSManagedObject to represent this remote object locally // [self newManagedObjectWithClassName:className forRecord:record]; } currentIndex++; } } } // // Once all NSManagedObjects are created in your context you can save the context to persist the objects // to your persistent store. In this case though you used an NSManagedObjectContext who has a parent context // so all changes will be pushed to the parent context // [managedObjectContext performBlockAndWait:^{ NSError *error = nil; if (![managedObjectContext save:&error]) { NSLog(@"Unable to save context for class %@", className); } }]; // // You are now done with the downloaded JSON responses so you can delete them to clean up after yourself, // then call your -executeSyncCompletedOperations to save off your master context and set the // syncInProgress flag to NO // [self deleteJSONDataRecordsForClassWithName:className]; [self executeSyncCompletedOperations];  }} 

This method must be called where Comment 2 sits as a placeholder in the downloadDataForRegisteredObjects method. When the HTTP request operations are completed, returned data is written to Core Data, and your app can then access them as required.

This is it, kids! This is the final downloadDataForRegisteredObjects method!

 - (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate { NSMutableArray *operations = [NSMutableArray array]; for (NSString *className in self.registeredClassesToSync) { NSDate *mostRecentUpdatedDate = nil; if (useUpdatedAtDate) { mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName:className]; } NSMutableURLRequest *request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentUpdatedDate]; AFHTTPRequestOperation *operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { if ([responseObject isKindOfClass:[NSDictionary class]]) { [self writeJSONResponse:responseObject toDiskForClassWithName:className]; } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"Request for class %@ failed with error: %@", className, error); }]; [operations addObject:operation]; }   [[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {   } completionBlock:^(NSArray *operations) {   [self processJSONDataRecordsIntoCoreData]; }];  } 

Last thing: to see the effect, go to SDDateTableViewController.m and update -viewDidAppear: and -viewDidDisappear: to register for the sync complete notification and reload the table:

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [[NSNotificationCenter defaultCenter] addObserverForName:@"SDSyncEngineSyncCompleted" object:nil queue:nil usingBlock:^(NSNotification *note) { [self loadRecordsFromCoreData];  [Self.tableView reloadData]; }]; }   - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SDSyncEngineSyncCompleted" object:nil];  } 

Run the project and see your records created in Parse come in to your App!

Manually trigger sync with remote service

Right now, your App will automatically sync when the user opens the App. This is generally an acceptable practice; however some users may want to refresh the dataset without leaving the App. You'll next implement a refresh button on both the Holiday table view and Birthday table view in order to accomplish this.

Start by opening Storyboard.storyboard, locating the Holidays Date Table View Controller and dragging a Bar Button Item onto the top left side of the navigation bar. Once you have dragged the bar button item onto the nav bar, change its Identifier attribute to “Refresh” as seen below:

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Now open the Assistant editor (the bow tie button in Xcode) and make sure SDDateTableViewController.h opens in the editor. Holding down the CTRL key, click and drag from the Refresh button to your interface to add a new IBAction named “refreshButtonTouched”:

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Do this same process again, but this time instead of creating an Action, create an Outlet for the button and name it 'refreshButton'. The outlet should be a strong reference, not a weak one — otherwise when the button is replaced by the activity indicator, it will be nil when the time comes to replace the button on the navigation bar.

Now select the Refresh button in your Storyboard, and while holding the Option/Alt key, click and drag it to your Birthdays Date Table View Controller to copy it, by copying you will also copy the outlets you already set up. Once you copy the button, with the Assistant editor still open you can hover the circles in the gutter next to your IBAction and IBOutlet to visually see the referenced outlets in the Storyboard, both buttons should highlight as shown below.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1


Open SDDateTableViewController.m and add an import for SDSyncEngine.h:

 #import "SDSyncEngine.h" 

Next complete the -refreshButtonTouched: method that was created for you during the Storyboard editing you just did, and add the following methods at the bottom of your class above @end:

 - (IBAction)refreshButtonTouched:(id)sender { [[SDSyncEngine sharedEngine] startSync]; }   - (void)checkSyncStatus { if ([[SDSyncEngine sharedEngine] syncInProgress]) { [self replaceRefreshButtonWithActivityIndicator]; } else { [self removeActivityIndicatorFromRefreshButton]; } }   - (void)replaceRefreshButtonWithActivityIndicator { UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)]; [activityIndicator setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)]; [activityIndicator startAnimating]; UIBarButtonItem *activityItem = [[UIBarButtonItem alloc] initWithCustomView:activityIndicator]; self.navigationItem.leftBarButtonItem = activityItem; }   - (void)removeActivityIndicatorFromRefreshButton { self.navigationItem.leftBarButtonItem = self.refreshButton; }   - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"syncInProgress"]) { [self checkSyncStatus];  }} 

-checkSyncStatus will ask the SDSyncEngine singleton if the sync is in progress. If so, it will call -replaceRefreshButtonWithActivityIndicator which does what it says,which is replace the refresh button with a UIActivityIndicatorView. Otherwise, the method will remove the UIActivityIndicatorView by replacing it with the refreshButton.

You will also implement -observeValueForKeyPath:ofObject:change:context: in order to observe changes in SDSyncEngine. To do so you will need to register for those notifications:

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self checkSyncStatus]; [[NSNotificationCenter defaultCenter] addObserverForName:@"SDSyncEngineSyncCompleted" object:nil queue:nil usingBlock:^(NSNotification *note) { [self loadRecordsFromCoreData];  [Self.tableView reloadData]; }]; [[SDSyncEngine sharedEngine] addObserver:self forKeyPath:@"syncInProgress" options:NSKeyValueObservingOptionNew context:nil]; }   - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SDSyncEngineSyncCompleted" object:nil]; [[SDSyncEngine sharedEngine] removeObserver:self forKeyPath:@"syncInProgress"];  } 

Update -viewDidAppear: and -viewDidDisappear: to look like the above, also notice when the view appears we check the sync status immediately.

Now when you build and run the App, you will see an activity indicator while the sync is in progress and a refresh button when the sync is inactive. Touching the refresh button fires off the sync engine.

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1
Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1
Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1

That's it, folks! This concludes Part 1 of the tutorial.

Where To Go From Here!

You can download the project at this stage here.

Stay tuned for part 2 of this tutorial, where you'll finish the app by adding the following features:

  1. Delete local objects when deleted on server
  2. Push locally created records to remote service
  3. Delete records on server when deleted locally

If you have any questions or comments, please join the forum discussion below!

Cómo sincronizar datos básicos con un servicio Web && num; 8211 & semi;  Parte 1

This is a post by iOS Tutorial Team Member Chris Wagner, an enthusiast in software engineering always trying to stay ahead of the curve. You can also find him on Google+.

Etiqueta:

Artículos relacionados

Popular

Último