Comment synchroniser les données de base de l'iPhone avec le serveur Web, puis les transférer vers d'autres appareils? [fermé]

293

Je travaille sur une méthode pour synchroniser les données de base stockées dans une application iPhone entre plusieurs appareils, comme un iPad ou un Mac. Il n'y a pas beaucoup (voire pas du tout) de cadres de synchronisation à utiliser avec Core Data sur iOS. Cependant, j'ai pensé au concept suivant:

  1. Une modification est apportée au magasin de données de base local et la modification est enregistrée. (a) Si le périphérique est en ligne, il essaie d'envoyer l'ensemble de modifications au serveur, y compris l'ID de périphérique de l'appareil qui a envoyé l'ensemble de modifications. (b) Si l'ensemble de modifications n'atteint pas le serveur, ou si l'appareil n'est pas en ligne, l'application ajoutera l'ensemble de modifications à une file d'attente à envoyer lorsqu'il sera en ligne.
  2. Le serveur, assis dans le cloud, fusionne les ensembles de modifications spécifiques qu'il reçoit avec sa base de données master.
  3. Après la fusion d'un jeu de modifications (ou d'une file d'attente de jeux de modifications) sur le serveur cloud, le serveur envoie tous ces jeux de modifications aux autres appareils enregistrés auprès du serveur à l'aide d'une sorte de système d'interrogation. (J'ai pensé à utiliser les services Push d'Apple, mais apparemment, selon les commentaires, ce n'est pas un système viable.)

Y a-t-il quelque chose d'extraordinaire auquel je dois penser? J'ai examiné les cadres REST tels que ObjectiveResource , Core Resource et RestfulCoreData . Bien sûr, tout cela fonctionne avec Ruby on Rails, auquel je ne suis pas lié, mais c'est un point de départ. Les principales exigences que j'ai pour ma solution sont:

  1. Toute modification doit être envoyée en arrière-plan sans interrompre le thread principal.
  2. Il doit utiliser le moins de bande passante possible.

J'ai réfléchi à un certain nombre de défis:

  1. Assurez-vous que les ID d'objet des différents magasins de données sur différents appareils sont connectés sur le serveur. C'est-à-dire que j'aurai une table d'ID d'objet et d'ID de périphérique, qui sont liés via une référence à l'objet stocké dans la base de données. J'aurai un enregistrement (DatabaseId [unique à cette table], ObjectId [unique à l'élément dans la base de données entière], Datafield1, Datafield2), le champ ObjectId référencera une autre table, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Ensuite, lorsque le périphérique envoie un ensemble de modifications, il transmet l'ID de périphérique et le objectId de l'objet de données principal dans le magasin de données local. Ensuite, mon serveur cloud vérifiera par rapport à l'ID d'objet et à l'ID de périphérique dans la table AllObjects, et trouvera l'enregistrement à modifier dans la table initiale.
  2. Toutes les modifications doivent être horodatées afin de pouvoir être fusionnées.
  3. L'appareil devra interroger le serveur, sans utiliser trop de batterie.
  4. Les appareils locaux devront également mettre à jour tout ce qui est en mémoire si / quand des modifications sont reçues du serveur.

Y a-t-il autre chose qui me manque ici? Quels types de cadres dois-je envisager pour rendre cela possible?

Jason
la source
5
Vous ne pouvez pas vous fier à la réception de notifications push. L'utilisateur peut simplement les toucher et lorsqu'une deuxième notification arrive, le système d'exploitation jette la première. Les notifications push IMO sont de toute façon un mauvais moyen de recevoir des mises à jour de synchronisation, car elles interrompent l'utilisateur. L'application doit lancer la synchronisation chaque fois qu'elle est lancée.
Ole Begemann
D'ACCORD. Merci pour l'information - en dehors d'interroger constamment le serveur et de vérifier les mises à jour au lancement, y a-t-il un moyen pour l'appareil d'obtenir des mises à jour? Je souhaite le faire fonctionner si l'application est ouverte sur plusieurs appareils simultanément.
Jason
1
(Je sais un peu tard, mais au cas où quelqu'un tomberait sur cela et se demande aussi) pour garder plusieurs appareils synchronisés simultanément, vous pourriez garder une connexion ouverte avec l'autre appareil ou un serveur, et envoyer des messages pour dire à l'autre appareil (s ) lorsqu'une mise à jour se produit. (par exemple, la façon dont IRC / messagerie instantanée fonctionne)
Dan2552
1
@ Dan2552: ce que vous décrivez est appelé [long polling] [ en.wikipedia.org/wiki/… et est une excellente idée, cependant les connexions ouvertes consomment beaucoup de batterie et de bande passante sur un appareil mobile.
johndodo
1
Voici un bon tutoriel de Ray Wenderlich sur la façon de synchroniser les données entre votre application et le service Web: raywenderlich.com/15916/…
JRG-Developer

Réponses:

144

Je suggère de lire attentivement et de mettre en œuvre la stratégie de synchronisation discutée par Dan Grover lors de la conférence iPhone 2009, disponible ici en tant que document pdf.

Il s'agit d'une solution viable et n'est pas si difficile à implémenter (Dan l'a implémentée dans plusieurs de ses applications), chevauchant la solution décrite par Chris. Pour une discussion théorique approfondie de la synchronisation, voir l'article de Russ Cox (MIT) et William Josephson (Princeton):

Synchronisation de fichiers avec des paires de temps vectorielles

qui s'applique également aux données de base avec quelques modifications évidentes. Cela fournit une stratégie de synchronisation globale beaucoup plus robuste et fiable, mais nécessite plus d'efforts pour être implémenté correctement.

ÉDITER:

Il semble que le fichier pdf de Grover ne soit plus disponible (lien brisé, mars 2015). MISE À JOUR: le lien est disponible via Way Back Machine ici

Le cadre Objective-C appelé ZSync et développé par Marcus Zarra est obsolète, étant donné qu'iCloud semble enfin prendre en charge la synchronisation correcte des données de base.

Massimo Cafaro
la source
Quelqu'un a-t-il un lien mis à jour pour la vidéo ZSync? De plus, ZSync est-il toujours maintenu? Je vois qu'il a été mis à jour pour la dernière fois en 2010.
Jeremie Weldin
Le dernier commit de ZSync sur github date de septembre 2010, ce qui m'amène à croire que Marcus a cessé de le supporter.
Périssable Dave
1
L'algorithme décrit par Dan Grover est assez bon. Cependant, cela ne fonctionnera pas avec un code de serveur multi-thread (donc: cela ne sera pas du tout mis à l'échelle) car il n'y a aucun moyen de s'assurer qu'un client ne manquera pas une mise à jour lorsque le temps est utilisé pour vérifier les nouvelles mises à jour . S'il vous plaît, corrigez-moi si je me trompe - je tuerais pour voir une implémentation fonctionnelle de cela.
masi
1
@Patt, je viens de vous envoyer le fichier pdf, comme demandé. Santé, Massimo Cafaro.
Massimo Cafaro
3
Les diapositives PDF de synchronisation des données multiplateforme manquantes de Dan Grover sont accessibles via Wayback Machine.
Matthew Kairys
272

J'ai fait quelque chose de similaire à ce que vous essayez de faire. Permettez-moi de vous dire ce que j'ai appris et comment je l'ai fait.

Je suppose que vous avez une relation un à un entre votre objet Core Data et le modèle (ou schéma db) sur le serveur. Vous voulez simplement synchroniser le contenu du serveur avec les clients, mais les clients peuvent également modifier et ajouter des données. Si j'ai bien compris, continuez à lire.

J'ai ajouté quatre champs pour aider à la synchronisation:

  1. sync_status - Ajoutez ce champ à votre modèle de données principal uniquement. Il est utilisé par l'application pour déterminer si vous avez une modification en attente sur l'élément. J'utilise les codes suivants: 0 signifie aucune modification, 1 signifie qu'il est mis en file d'attente pour être synchronisé avec le serveur et 2 signifie qu'il s'agit d'un objet temporaire et peut être purgé.
  2. is_deleted - Ajoutez ceci au serveur et au modèle de données principal. L'événement de suppression ne doit pas réellement supprimer une ligne de la base de données ou de votre modèle client, car il ne vous laisse rien à synchroniser. En ayant ce simple drapeau booléen, vous pouvez mettre is_deleted à 1, le synchroniser, et tout le monde sera content. Vous devez également modifier le code sur le serveur et le client pour interroger les éléments non supprimés avec "is_deleted = 0".
  3. last_modified - Ajoutez ceci au serveur et au modèle de données principal. Ce champ doit être automatiquement mis à jour avec la date et l'heure actuelles par le serveur chaque fois que quelque chose change sur cet enregistrement. Il ne doit jamais être modifié par le client.
  4. guid - Ajoutez un champ d' ID globalement unique (voir http://en.wikipedia.org/wiki/Globally_unique_identifier ) au serveur et au modèle de données de base. Ce champ devient la clé primaire et devient important lors de la création de nouveaux enregistrements sur le client. Normalement, votre clé primaire est un entier incrémenté sur le serveur, mais nous devons garder à l'esprit que le contenu pourrait être créé hors ligne et synchronisé plus tard. Le GUID nous permet de créer une clé tout en étant hors ligne.

Sur le client, ajoutez du code pour définir sync_status sur 1 sur votre objet modèle chaque fois que quelque chose change et doit être synchronisé avec le serveur. Les nouveaux objets de modèle doivent générer un GUID.

La synchronisation est une seule demande. La demande contient:

  • Horodatage MAX last_modified de vos objets de modèle. Cela indique au serveur que vous souhaitez uniquement des modifications après cet horodatage.
  • Un tableau JSON contenant tous les éléments avec sync_status = 1.

Le serveur reçoit la demande et fait ceci:

  • Il prend le contenu du tableau JSON et modifie ou ajoute les enregistrements qu'il contient. Le champ last_modified est automatiquement mis à jour.
  • Le serveur renvoie un tableau JSON contenant tous les objets avec un horodatage last_modified supérieur à l'horodatage envoyé dans la demande. Cela inclura les objets qu'il vient de recevoir, ce qui sert à confirmer que l'enregistrement a été correctement synchronisé avec le serveur.

L'application reçoit la réponse et fait ceci:

  • Il prend le contenu du tableau JSON et modifie ou ajoute les enregistrements qu'il contient. Chaque enregistrement obtient un sync_status de 0.

J'espère que ça aide. J'ai utilisé le mot enregistrement et modèle de façon interchangeable, mais je pense que vous avez compris l'idée. Bonne chance.

chris
la source
2
Le champ last_modified existe également dans la base de données locale, mais il n'est pas mis à jour par l'horloge de l'iPhone. Il est défini par le serveur et synchronisé en retour. La date MAX (last_modified) est ce que l'application envoie au serveur pour lui dire de renvoyer tout ce qui a été modifié après cette date.
chris
3
Une valeur globale sur le client pourrait remplacer MAX(last_modified), mais ce serait redondant car MAX(last_modified)suffisant. Le sync_statusa un autre rôle. Comme je l'ai écrit plus tôt, MAX(last_modified)détermine ce qui doit être synchronisé depuis le serveur, tout en sync_statusdétermine ce qui doit être synchronisé avec le serveur.
chris
2
@Flex_Addicted Merci. Oui, vous devrez répliquer les champs pour chaque entité que vous souhaitez synchroniser. Cependant, vous devez être plus prudent lors de la synchronisation d'un modèle avec une relation (par exemple, 1 à plusieurs).
chris
2
@BenPackard - Vous avez raison. L'approche ne fait aucune résolution de conflit, donc le dernier client gagnera. Je n'ai pas eu à gérer cela dans mes applications car les enregistrements sont modifiés par un seul utilisateur. Je serais curieux de savoir comment vous résolvez cela.
chris
2
Bonjour @noilly, considérez le cas suivant: vous apportez des modifications à un objet local et devez le synchroniser avec le serveur. La synchronisation ne peut avoir lieu que des heures ou des jours plus tard (par exemple, si vous êtes hors ligne depuis un certain temps), et pendant ce temps, l'application peut avoir été arrêtée et redémarrée plusieurs fois. Dans ce cas, les méthodes sur NSManagedObjectContext n'aideraient pas beaucoup.
chris
11

Si vous cherchez toujours un chemin à parcourir, consultez le mobile Couchbase. Cela fait tout ce que vous voulez. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

radiospiel
la source
3
Cela ne fait ce que vous voulez que si vous pouvez exprimer vos données sous forme de documents plutôt que de données relationnelles. Il y a des contournements, mais ils ne sont pas toujours beaux ni valent la peine.
Jeremie Weldin
les documents sont suffisants pour les petites applications
Hai Feng Kao
@radiospiel Votre lien est cassé
Mick
Cela ajoutera également une dépendance selon laquelle le backend doit être écrit dans Couchbase DB. Même moi, j'ai commencé avec l'idée de NOSQL pour la synchronisation, mais je ne peux pas limiter mon backend à NOSQL car nous avons MS SQL en cours d'exécution dans le backend.
thesummersign
@Mick: cela semble fonctionner à nouveau (ou quelqu'un a corrigé le lien? Merci)
radiospiel
7

Similaire comme @Cris, j'ai implémenté une classe pour la synchronisation entre le client et le serveur et résolu tous les problèmes connus jusqu'à présent (envoyer / recevoir des données vers / depuis le serveur, fusionner les conflits basés sur les horodatages, supprimer les entrées en double dans des conditions de réseau peu fiables, synchroniser les données imbriquées et fichiers etc ..)

Vous indiquez simplement à la classe quelle entité et quelles colonnes doivent être synchronisées et où se trouve votre serveur.

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

Vous pouvez trouver la source, un exemple de travail et plus d'instructions ici: github.com/knagode/M3Synchronization .

knagode
la source
Sera-ce correct si nous modifions l'heure de l'appareil à une valeur anormale?
Golden
5

Remarquez que l'utilisateur doit mettre à jour les données via une notification push. Utilisez un fil d'arrière-plan dans l'application pour vérifier les données locales et les données sur le serveur cloud, tandis que le changement se produit sur le serveur, modifiez les données locales, et vice versa.

Je pense donc que la partie la plus difficile est d'estimer les données de quel côté est invalidé.

J'espère que cela peut vous aider

Stan
la source
5

Je viens de publier la première version de ma nouvelle API Core Data Cloud Syncing, connue sous le nom de SynCloud. SynCloud a beaucoup de différences avec iCloud car il permet une interface de synchronisation multi-utilisateurs. Il est également différent des autres API de synchronisation car il permet des données relationnelles multi-tables.

Veuillez en savoir plus sur http://www.syncloudapi.com

Construit avec le SDK iOS 6, il est très à jour au 27/09/2012.

logan
la source
5
Bienvenue dans Stack Overflow! Merci d'avoir posté votre réponse! Veuillez lire attentivement la FAQ sur l' autopromotion.
Andrew Barber
5

Je pense qu'une bonne solution au problème du GUID est le "système d'identification distribué". Je ne sais pas quel est le terme correct, mais je pense que c'est ainsi que les documents du serveur MS SQL l'appelaient (SQL utilise / a utilisé cette méthode pour les bases de données distribuées / synchronisées). C'est assez simple:

Le serveur attribue tous les ID. Chaque fois qu'une synchronisation est effectuée, la première chose qui est vérifiée est "Combien d'ID reste-t-il sur ce client?" Si le client est faible, il demande au serveur un nouveau bloc d'ID. Le client utilise ensuite les ID de cette plage pour les nouveaux enregistrements. Cela fonctionne très bien pour la plupart des besoins, si vous pouvez attribuer un bloc suffisamment grand pour qu'il ne "jamais" s'épuise avant la prochaine synchronisation, mais pas si grand que le serveur s'épuise au fil du temps. Si le client est épuisé, la gestion peut être assez simple, dites simplement à l'utilisateur "désolé que vous ne pouvez pas ajouter plus d'éléments avant de synchroniser" ... s'il ajoute autant d'éléments, ne devrait-il pas se synchroniser pour éviter les données périmées des problèmes de toute façon?

Je pense que cela est supérieur à l'utilisation de GUID aléatoires car les GUID aléatoires ne sont pas sûrs à 100% et doivent généralement être beaucoup plus longs qu'un ID standard (128 bits contre 32 bits). Vous avez généralement des index par ID et gardez souvent des numéros d'ID en mémoire, il est donc important de les garder petits.

Je ne voulais pas vraiment poster comme réponse, mais je ne sais pas si quelqu'un verrait un commentaire, et je pense que c'est important pour ce sujet et non inclus dans les autres réponses.

eselk
la source
2

Vous devez d'abord repenser le nombre de données, de tables et de relations que vous aurez. Dans ma solution, j'ai implémenté la synchronisation via les fichiers Dropbox. J'observe les changements dans le MOC principal et enregistre ces données dans des fichiers (chaque ligne est enregistrée au format json gzippé). Si une connexion Internet fonctionne, je vérifie s'il y a des modifications sur Dropbox (Dropbox me donne des modifications delta), les télécharge et les fusionne (dernières victoires), et enfin place les fichiers modifiés. Avant la synchronisation, je mets le fichier de verrouillage sur Dropbox pour empêcher d'autres clients de synchroniser des données incomplètes. Lors du téléchargement des modifications, il est sûr que seules des données partielles sont téléchargées (par exemple, une connexion Internet perdue). Lorsque le téléchargement est terminé (entièrement ou partiellement), il commence à charger des fichiers dans Core Data. Lorsqu'il existe des relations non résolues (tous les fichiers ne sont pas téléchargés), il arrête le chargement des fichiers et essaie de terminer le téléchargement plus tard. Les relations sont stockées uniquement sous forme de GUID, donc je peux facilement vérifier quels fichiers à charger pour avoir une intégrité complète des données. La synchronisation démarre après que des modifications ont été apportées aux données de base. S'il n'y a pas de modifications, il vérifie les modifications sur Dropbox toutes les quelques minutes et au démarrage de l'application. De plus, lorsque des modifications sont envoyées au serveur, j'envoie une diffusion à d'autres appareils pour les informer des modifications, afin qu'ils puissent se synchroniser plus rapidement. Chaque entité synchronisée possède une propriété GUID (guid est également utilisé comme nom de fichier pour les fichiers d'échange). J'ai également une base de données Sync où je stocke la révision Dropbox de chaque fichier (je peux le comparer lorsque le delta Dropbox réinitialise son état). Les fichiers contiennent également le nom de l'entité, l'état (supprimé / non supprimé), guid (identique au nom de fichier), la révision de la base de données (pour détecter les migrations de données ou pour éviter la synchronisation avec des versions jamais d'application) et bien sûr les données (si la ligne n'est pas supprimée). donc je peux facilement vérifier quels fichiers à charger pour avoir une intégrité complète des données. La synchronisation démarre après que des modifications ont été apportées aux données de base. S'il n'y a pas de modifications, il vérifie les modifications sur Dropbox toutes les quelques minutes et au démarrage de l'application. De plus, lorsque des modifications sont envoyées au serveur, j'envoie une diffusion à d'autres appareils pour les informer des modifications, afin qu'ils puissent se synchroniser plus rapidement. Chaque entité synchronisée possède une propriété GUID (guid est également utilisé comme nom de fichier pour les fichiers d'échange). J'ai également une base de données Sync où je stocke la révision Dropbox de chaque fichier (je peux le comparer lorsque le delta Dropbox réinitialise son état). Les fichiers contiennent également le nom de l'entité, l'état (supprimé / non supprimé), guid (identique au nom de fichier), la révision de la base de données (pour détecter les migrations de données ou pour éviter la synchronisation avec des versions jamais d'application) et bien sûr les données (si la ligne n'est pas supprimée). donc je peux facilement vérifier quels fichiers à charger pour avoir une intégrité complète des données. La synchronisation démarre après que des modifications ont été apportées aux données de base. S'il n'y a pas de modifications, il vérifie les modifications sur Dropbox toutes les quelques minutes et au démarrage de l'application. De plus, lorsque des modifications sont envoyées au serveur, j'envoie une diffusion à d'autres appareils pour les informer des modifications, afin qu'ils puissent se synchroniser plus rapidement. Chaque entité synchronisée possède une propriété GUID (guid est également utilisé comme nom de fichier pour les fichiers d'échange). J'ai également une base de données Sync où je stocke la révision Dropbox de chaque fichier (je peux le comparer lorsque le delta Dropbox réinitialise son état). Les fichiers contiennent également le nom de l'entité, l'état (supprimé / non supprimé), guid (identique au nom de fichier), la révision de la base de données (pour détecter les migrations de données ou pour éviter la synchronisation avec des versions jamais d'application) et bien sûr les données (si la ligne n'est pas supprimée).

Cette solution fonctionne pour des milliers de fichiers et environ 30 entités. Au lieu de Dropbox, je pourrais utiliser le magasin de clés / valeurs comme service Web REST que je veux faire plus tard, mais je n'ai pas le temps pour cela :) Pour l'instant, à mon avis, ma solution est plus fiable qu'iCloud et, ce qui est très important, J'ai un contrôle total sur son fonctionnement (principalement parce que c'est mon propre code).

Une autre solution consiste à enregistrer les modifications MOC en tant que transactions - il y aura beaucoup moins de fichiers échangés avec le serveur, mais il est plus difficile d'effectuer le chargement initial dans le bon ordre dans des données de base vides. iCloud fonctionne de cette façon, et d'autres solutions de synchronisation ont également une approche similaire, par exemple TICoreDataSync .

-- METTRE À JOUR

Après un certain temps, j'ai migré vers Ensembles - je recommande cette solution plutôt que de réinventer la roue.

thom_ek
la source