¿Cómo sincronizar los datos principales del iPhone con el servidor web y luego enviarlos a otros dispositivos? [cerrado]


293

He estado trabajando en un método para sincronizar los datos centrales almacenados en una aplicación de iPhone entre múltiples dispositivos, como un iPad o una Mac. No hay muchos marcos de sincronización (si es que los hay) para usar con Core Data en iOS. Sin embargo, he estado pensando en el siguiente concepto:

  1. Se realiza un cambio en el almacén de datos central local y se guarda el cambio. (a) Si el dispositivo está en línea, intenta enviar el conjunto de cambios al servidor, incluida la ID del dispositivo que envió el conjunto de cambios. (b) Si el conjunto de cambios no llega al servidor, o si el dispositivo no está en línea, la aplicación agregará el conjunto de cambios a una cola para enviarlo cuando esté en línea.
  2. El servidor, sentado en la nube, combina los conjuntos de cambios específicos que recibe con su base de datos maestra.
  3. Después de fusionar un conjunto de cambios (o una cola de conjuntos de cambios) en el servidor de la nube, el servidor empuja todos esos conjuntos de cambios a los otros dispositivos registrados en el servidor utilizando algún tipo de sistema de sondeo. (Pensé en usar los servicios Push de Apple, pero aparentemente según los comentarios, este no es un sistema viable).

¿Hay algo elegante en lo que deba pensar? He examinado los marcos REST como ObjectiveResource , Core Resource y RestfulCoreData . Por supuesto, todos estos están trabajando con Ruby on Rails, a lo que no estoy vinculado, pero es un lugar para comenzar. Los principales requisitos que tengo para mi solución son:

  1. Cualquier cambio debe enviarse en segundo plano sin pausar el hilo principal.
  2. Debe usar el menor ancho de banda posible.

He pensado en varios de los desafíos:

  1. Asegurarse de que las ID de objeto para los diferentes almacenes de datos en diferentes dispositivos estén conectadas en el servidor. Es decir, tendré una tabla de ID de objeto e ID de dispositivo, que están vinculadas mediante una referencia al objeto almacenado en la base de datos. Tendré un registro (DatabaseId [exclusivo de esta tabla], ObjectId [exclusivo del elemento en toda la base de datos], Datafield1, Datafield2), el campo ObjectId hará referencia a otra tabla, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Luego, cuando el dispositivo empuja hacia arriba un conjunto de cambios, pasará la Id. Del dispositivo y la Id. Del objeto desde el objeto de datos central en el almacén de datos local. Luego, mi servidor en la nube verificará el Id. Del objeto y el Id. Del dispositivo en la tabla AllObjects, y encontrará el registro para cambiar en la tabla inicial.
  2. Todos los cambios deben tener una marca de tiempo, para que puedan fusionarse.
  3. El dispositivo tendrá que sondear el servidor, sin usar demasiada batería.
  4. Los dispositivos locales también deberán actualizar todo lo que esté guardado en la memoria si / cuando se reciben cambios del servidor.

¿Hay algo más que me falta aquí? ¿Qué tipo de marcos debo mirar para hacer esto posible?


55
No puede confiar en recibir notificaciones push. El usuario simplemente puede eliminarlos y cuando llega una segunda notificación, el sistema operativo arroja la primera. Las notificaciones push IMO son una mala manera de recibir actualizaciones de sincronización, de todos modos, porque interrumpen al usuario. La aplicación debe iniciar la sincronización cada vez que se inicia.
Ole Begemann

OKAY. Gracias por la información: además de sondear constantemente el servidor y buscar actualizaciones durante el inicio, ¿hay alguna forma de que el dispositivo obtenga actualizaciones? Estoy interesado en hacerlo funcionar si la aplicación está abierta en múltiples dispositivos simultáneamente.
Jason

1
(Lo sé un poco tarde, pero en caso de que alguien se encuentre con esto y también se pregunte) para mantener varios dispositivos sincronizados simultáneamente, podría mantener una conexión abierta con el otro dispositivo o un servidor, y enviar mensajes para decirle a los otros dispositivos ) cuando se produce una actualización. (por ejemplo, funciona de la manera de mensajería IRC / instantánea)
Dan2552

1
@ Dan2552: lo que describe se conoce como [sondeo largo] [ en.wikipedia.org/wiki/… y es una gran idea, sin embargo, las conexiones abiertas consumen mucha batería y ancho de banda en un dispositivo móvil.
johndodo

1
Aquí hay un buen tutorial de Ray Wenderlich sobre cómo sincronizar datos entre su aplicación y el servicio web: raywenderlich.com/15916/…
JRG-Developer

Respuestas:


144

Sugiero leer detenidamente e implementar la estrategia de sincronización discutida por Dan Grover en la conferencia de iPhone 2009, disponible aquí como documento pdf.

Esta es una solución viable y no es tan difícil de implementar (Dan implementó esto en varias de sus aplicaciones), superponiendo la solución descrita por Chris. Para una discusión teórica en profundidad sobre la sincronización, vea el artículo de Russ Cox (MIT) y William Josephson (Princeton):

Sincronización de archivos con pares de tiempo de vectores

que se aplica igualmente bien a los datos centrales con algunas modificaciones obvias. Esto proporciona una estrategia general de sincronización mucho más robusta y confiable, pero requiere más esfuerzo para implementarse correctamente.

EDITAR:

Parece que el archivo pdf de Grover ya no está disponible (enlace roto, marzo de 2015). ACTUALIZACIÓN: el enlace está disponible a través de Way Back Machine aquí

El marco Objective-C llamado ZSync y desarrollado por Marcus Zarra ha quedado en desuso, dado que iCloud finalmente parece admitir la sincronización correcta de los datos centrales.


¿Alguien tiene un enlace actualizado para el video ZSync? Además, ¿todavía se mantiene ZSync? Veo que se actualizó por última vez en 2010.
Jeremie Weldin

El último compromiso de ZSync con github fue en septiembre de 2010, lo que me lleva a creer que Marcus dejó de apoyarlo.
Perecedero Dave

1
El algoritmo descrito por Dan Grover es bastante bueno. Sin embargo, no funcionará con un código de servidor multiproceso (por lo tanto: esto no escalará en absoluto) ya que no hay forma de asegurarse de que un cliente no se pierda una actualización cuando se utiliza el tiempo para buscar nuevas actualizaciones . Por favor, corríjame si estoy equivocado, mataría por ver una implementación funcional de esto.
masi

1
@Patt, acabo de enviarle el archivo pdf, según lo solicitado. Saludos, Massimo Cafaro.
Massimo Cafaro

3
Las diapositivas PDF faltantes de sincronización de datos multiplataforma de Dan Grover son accesibles a través de Wayback Machine.
Matthew Kairys

272

He hecho algo similar a lo que intentas hacer. Déjame decirte lo que he aprendido y cómo lo hice.

Supongo que tiene una relación uno a uno entre su objeto Core Data y el modelo (o esquema db) en el servidor. Simplemente desea mantener sincronizados los contenidos del servidor con los clientes, pero los clientes también pueden modificar y agregar datos. Si acerté, sigue leyendo.

Agregué cuatro campos para ayudar con la sincronización:

  1. sync_status : agregue este campo solo a su modelo de datos principales. La aplicación la utiliza para determinar si tiene un cambio pendiente en el artículo. Utilizo los siguientes códigos: 0 significa que no hay cambios, 1 significa que está en cola para sincronizarse con el servidor y 2 significa que es un objeto temporal y se puede purgar.
  2. is_deleted : agregue esto al servidor y al modelo de datos principales. El evento Delete no debería eliminar una fila de la base de datos o de su modelo de cliente porque no le deja nada para sincronizar. Al tener este indicador booleano simple, puede establecer is_deleted en 1, sincronizarlo y todos estarán felices. También debe modificar el código en el servidor y el cliente para consultar elementos no eliminados con "is_deleted = 0".
  3. last_modified : agregue esto al servidor y al modelo de datos principales. Este campo debe ser actualizado automáticamente con la fecha y hora actuales por el servidor cada vez que algo cambie en ese registro. Nunca debe ser modificado por el cliente.
  4. guid : agregue un campo de identificación global único (consulte http://en.wikipedia.org/wiki/Globally_unique_identifier ) al servidor y al modelo de datos principales. Este campo se convierte en la clave principal y se vuelve importante al crear nuevos registros en el cliente. Normalmente, su clave principal es un número entero incremental en el servidor, pero debemos tener en cuenta que el contenido podría crearse sin conexión y sincronizarse más tarde. El GUID nos permite crear una clave mientras estamos desconectados.

En el cliente, agregue código para establecer sync_status en 1 en su objeto modelo cada vez que algo cambie y necesite sincronizarse con el servidor. Los nuevos objetos modelo deben generar un GUID.

La sincronización es una sola solicitud. La solicitud contiene:

  • La marca de tiempo MAX last_modified de sus objetos modelo. Esto le dice al servidor que solo desea cambios después de esta marca de tiempo.
  • Una matriz JSON que contiene todos los elementos con sync_status = 1.

El servidor recibe la solicitud y hace esto:

  • Toma el contenido de la matriz JSON y modifica o agrega los registros que contiene. El campo last_modified se actualiza automáticamente.
  • El servidor devuelve una matriz JSON que contiene todos los objetos con una marca de tiempo last_modified mayor que la marca de tiempo enviada en la solicitud. Esto incluirá los objetos que acaba de recibir, lo que sirve como un reconocimiento de que el registro se sincronizó correctamente con el servidor.

La aplicación recibe la respuesta y hace esto:

  • Toma el contenido de la matriz JSON y modifica o agrega los registros que contiene. Cada registro establece un sync_status de 0.

Espero que eso ayude. Utilicé el registro de palabras y el modelo indistintamente, pero creo que entiendes la idea. Buena suerte.


2
El campo last_modified también existe en la base de datos local, pero el reloj del iPhone no lo actualiza. Lo establece el servidor y se sincroniza de nuevo. La fecha MAX (last_modified) es lo que la aplicación envía al servidor para indicarle que envíe de vuelta todo lo modificado después de esa fecha.
Chris

3
Un valor global en el cliente podría reemplazar MAX(last_modified), pero eso sería redundante ya que es MAX(last_modified)suficiente. El sync_statustiene otro papel. Como escribí anteriormente, MAX(last_modified)determina qué debe sincronizarse desde el servidor, mientras sync_statusdetermina qué necesita sincronizarse con el servidor.
Chris

2
@Flex_Addicted Gracias. Sí, necesitaría replicar los campos para cada entidad que desea sincronizar. Sin embargo, debe tener mayor cuidado al sincronizar un modelo con una relación (por ejemplo, 1 a muchos).
Chris

2
@BenPackard: tienes razón. El enfoque no resuelve ningún conflicto, por lo que el último cliente ganará. No he tenido que lidiar con esto en mis aplicaciones ya que los registros son editados por un solo usuario. Me gustaría saber cómo resuelves esto.
Chris

2
Hola @noilly, considere el siguiente caso: realiza cambios en un objeto local y necesita sincronizarlo nuevamente con el servidor. La sincronización solo puede ocurrir horas o días después (por ejemplo, si ha estado desconectado durante un tiempo), y en ese momento la aplicación puede haberse apagado y reiniciado varias veces. En este caso, los métodos en NSManagedObjectContext no ayudarían mucho.
Chris

11

Si todavía está buscando un camino a seguir, busque en el móvil Couchbase. Esto básicamente hace todo lo que quieres. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )


3
Esto solo hace lo que quiere si puede expresar sus datos como documentos en lugar de datos relacionales. Hay soluciones, pero no siempre son bonitas o valen la pena.
Jeremie Weldin

documentos son suficientes para pequeñas aplicaciones
Hai Feng Kao

@radiospiel Su enlace está roto
Mick

Esto también agregará una dependencia de que el backend debe escribirse en Couchbase DB. Incluso comencé con la idea de NOSQL para la sincronización, pero no puedo restringir mi back-end para que sea NOSQL ya que tenemos MS SQL ejecutándose en el back-end.
thesummersign

@Mick: parece funcionar de nuevo (¿o alguien arregló el enlace? Gracias)
radiospiel

7

Similar a @Cris, he implementado una clase para la sincronización entre el cliente y el servidor y he resuelto todos los problemas conocidos hasta ahora (enviar / recibir datos a / desde el servidor, fusionar conflictos basados ​​en marcas de tiempo, eliminar entradas duplicadas en condiciones de red poco confiables, sincronizar datos anidados y archivos, etc.)

Simplemente le dice a la clase qué entidad y qué columnas debe sincronizar y dónde está su servidor.

M3Synchronization * syncEntity = [[M3Synchronization alloc] initForClass: @"Car"
                                                              andContext: context
                                                            andServerUrl: kWebsiteUrl
                                             andServerReceiverScriptName: kServerReceiverScript
                                              andServerFetcherScriptName: kServerFetcherScript
                                                    ansSyncedTableFields:@[@"licenceNumber", @"manufacturer", @"model"]
                                                    andUniqueTableFields:@[@"licenceNumber"]];


syncEntity.delegate = self; // delegate should implement onComplete and onError methods
syncEntity.additionalPostParamsDictionary = ... // add some POST params to authenticate current user

[syncEntity sync];

Puede encontrar la fuente, el ejemplo de trabajo y más instrucciones aquí: github.com/knagode/M3Synchronization .


¿Estará bien si cambiamos el tiempo del dispositivo a un valor anormal?
Golden

5

Notifique al usuario que actualice los datos mediante notificación push. Use un hilo de fondo en la aplicación para verificar los datos locales y los datos en el servidor de la nube, mientras que el cambio ocurre en el servidor, cambie los datos locales, y viceversa.

Entonces, creo que la parte más difícil es estimar los datos en qué lado se invalida.

Espero que esto pueda ayudarte


5

Acabo de publicar la primera versión de mi nueva API de sincronización de Core Data Cloud, conocida como SynCloud. SynCloud tiene muchas diferencias con iCloud porque permite una interfaz de sincronización multiusuario. También es diferente de otras API de sincronización porque permite datos relacionales de varias tablas.

Obtenga más información en http://www.syncloudapi.com

Construido con iOS 6 SDK, está muy actualizado a partir del 27/09/2012.


55
¡Bienvenido a Stack Overflow! ¡Gracias por publicar tu respuesta! Asegúrese de leer atentamente las preguntas frecuentes sobre autopromoción.
Andrew Barber

5

Creo que una buena solución al problema de GUID es el "sistema de identificación distribuido". No estoy seguro de cuál es el término correcto, pero creo que así lo llamaban los documentos del servidor MS SQL (SQL usa / usa este método para bases de datos distribuidas / sincronizadas). Es bastante simple:

El servidor asigna todas las ID. Cada vez que se realiza una sincronización, lo primero que se verifica es "¿Cuántas ID me quedan en este cliente?" Si el cliente se está agotando, le pide al servidor un nuevo bloque de ID. El cliente luego usa ID en ese rango para nuevos registros. Esto funciona muy bien para la mayoría de las necesidades, si puede asignar un bloque lo suficientemente grande como para que "nunca" se agote antes de la próxima sincronización, pero no tan grande que el servidor se agote con el tiempo. Si el cliente alguna vez se agota, el manejo puede ser bastante simple, solo dígale al usuario "lo siento, no puede agregar más elementos hasta que sincronice" ... si está agregando esa cantidad de elementos, ¿no deberían sincronizarse para evitar datos obsoletos? problemas de todos modos?

Creo que esto es superior al uso de GUID aleatorios porque los GUID aleatorios no son 100% seguros y, por lo general, deben ser mucho más largos que una ID estándar (128 bits frente a 32 bits). Por lo general, tiene índices por ID y, a menudo, mantiene los números de ID en la memoria, por lo que es importante mantenerlos pequeños.

Realmente no quería publicar como respuesta, pero no sé si alguien lo vería como un comentario, y creo que es importante para este tema y no está incluido en otras respuestas.


2

Primero debe repensar cuántos datos, tablas y relaciones tendrá. En mi solución, implementé la sincronización a través de archivos de Dropbox. Observo cambios en el MOC principal y guardo estos datos en archivos (cada fila se guarda como json comprimido). Si hay una conexión a Internet que funciona, verifico si hay cambios en Dropbox (Dropbox me da cambios delta), los descargo y los combino (últimas victorias), y finalmente pongo los archivos modificados. Antes de la sincronización, pongo el archivo de bloqueo en Dropbox para evitar que otros clientes sincronicen datos incompletos. Al descargar los cambios, es seguro que solo se descarguen datos parciales (por ejemplo, pérdida de conexión a Internet). Cuando finaliza la descarga (total o parcial), comienza a cargar archivos en Core Data. Cuando hay relaciones no resueltas (no se descargan todos los archivos), deja de cargar archivos e intenta finalizar la descarga más tarde. Las relaciones se almacenan solo como GUID, por lo que puedo verificar fácilmente qué archivos cargar para tener una integridad de datos completa. La sincronización comienza después de realizar cambios en los datos principales. Si no hay cambios, comprueba los cambios en Dropbox cada pocos minutos y en el inicio de la aplicación. Además, cuando se envían cambios al servidor, envío una transmisión a otros dispositivos para informarles sobre los cambios, para que puedan sincronizarse más rápido. Cada entidad sincronizada tiene una propiedad GUID (guid se usa también como un nombre de archivo para los archivos de intercambio). También tengo una base de datos de sincronización donde almaceno la revisión de Dropbox de cada archivo (puedo compararlo cuando Dropbox delta restablece su estado). Los archivos también contienen el nombre de la entidad, el estado (eliminado / no eliminado), guid (igual que el nombre del archivo), revisión de la base de datos (para detectar migraciones de datos o para evitar la sincronización con versiones que nunca son de la aplicación) y, por supuesto, los datos (si la fila no se elimina). así puedo verificar fácilmente qué archivos cargar para tener integridad de datos completa. La sincronización comienza después de realizar cambios en los datos principales. Si no hay cambios, comprueba los cambios en Dropbox cada pocos minutos y en el inicio de la aplicación. Además, cuando se envían cambios al servidor, envío una transmisión a otros dispositivos para informarles sobre los cambios, para que puedan sincronizarse más rápido. Cada entidad sincronizada tiene una propiedad GUID (guid se usa también como un nombre de archivo para los archivos de intercambio). También tengo una base de datos de sincronización donde almaceno la revisión de Dropbox de cada archivo (puedo compararlo cuando Dropbox delta restablece su estado). Los archivos también contienen el nombre de la entidad, el estado (eliminado / no eliminado), guid (igual que el nombre del archivo), revisión de la base de datos (para detectar migraciones de datos o para evitar la sincronización con versiones que nunca son de la aplicación) y, por supuesto, los datos (si la fila no se elimina). así puedo verificar fácilmente qué archivos cargar para tener integridad de datos completa. La sincronización comienza después de realizar cambios en los datos principales. Si no hay cambios, comprueba los cambios en Dropbox cada pocos minutos y en el inicio de la aplicación. Además, cuando se envían cambios al servidor, envío una transmisión a otros dispositivos para informarles sobre los cambios, para que puedan sincronizarse más rápido. Cada entidad sincronizada tiene una propiedad GUID (guid se usa también como un nombre de archivo para los archivos de intercambio). También tengo una base de datos de sincronización donde almaceno la revisión de Dropbox de cada archivo (puedo compararlo cuando Dropbox delta restablece su estado). Los archivos también contienen el nombre de la entidad, el estado (eliminado / no eliminado), guid (igual que el nombre del archivo), revisión de la base de datos (para detectar migraciones de datos o para evitar la sincronización con versiones que nunca son de la aplicación) y, por supuesto, los datos (si la fila no se elimina).

Esta solución funciona para miles de archivos y alrededor de 30 entidades. En lugar de Dropbox, podría usar el almacén de claves / valores como servicio web REST, lo que quiero hacer más tarde, pero no tengo tiempo para esto :) Por ahora, en mi opinión, mi solución es más confiable que iCloud y, lo cual es muy importante, Tengo control total sobre cómo funciona (principalmente porque es mi propio código).

Otra solución es guardar los cambios de MOC como transacciones: se intercambiarán muchos menos archivos con el servidor, pero es más difícil hacer la carga inicial en el orden correcto en los datos centrales vacíos. iCloud funciona de esta manera, y también otras soluciones de sincronización tienen un enfoque similar, por ejemplo, TICoreDataSync .

- ACTUALIZACIÓN

Después de un tiempo, migré a Ensembles : recomiendo esta solución en lugar de reinventar la rueda.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.