297 votes

Comment synchroniser les données de base de l'iPhone avec le serveur web, puis les pousser vers d'autres appareils ?

J'ai travaillé sur une méthode permettant de synchroniser les données de base stockées dans une application iPhone entre plusieurs appareils, comme un iPad ou un Mac. Il n'existe pas beaucoup (voire pas du tout) de cadres de synchronisation à utiliser avec les données de base sur iOS. Cependant, j'ai réfléchi 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 dispositif est en ligne, il essaie d'envoyer le jeu de modifications au serveur, y compris l'ID du dispositif qui a envoyé le jeu de modifications. (b) Si le jeu de modifications n'atteint pas le serveur, ou si le dispositif n'est pas en ligne, l'application ajoute le jeu de modifications à une file d'attente pour l'envoyer lorsqu'il sera en ligne.
  2. Le serveur, situé dans le nuage, fusionne les ensembles de modifications spécifiques qu'il reçoit avec sa base de données principale.
  3. Après qu'un ensemble de modifications (ou une file d'attente d'ensembles de modifications) est fusionné sur le serveur en nuage, le serveur pousse tous ces ensembles de modifications vers les autres appareils enregistrés auprès du serveur en utilisant 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 exploitable).

Y a-t-il quelque chose de spécial auquel je dois penser ? J'ai regardé les frameworks REST tels que ObjectifRessource , Ressource principale y RestfulCoreData . Bien sûr, toutes ces applications fonctionnent 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 les suivantes :

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

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

  1. S'assurer que les ID d'objets pour les différents magasins de données sur les différents appareils sont attachés sur le serveur. C'est-à-dire que je disposerai d'une table d'identifiants d'objets et d'identifiants de dispositifs, qui sont liés via une référence à l'objet stocké dans la base de données. J'aurai un enregistrement (DatabaseId [unique pour cette table], ObjectId [unique pour l'objet dans toute la base de données], Datafield1, Datafield2), le champ ObjectId fera référence à une autre table, AllObjects : (ObjectId, DeviceId, DeviceObjectId). Ensuite, lorsque le dispositif envoie un ensemble de modifications, il transmet l'identifiant du dispositif et l'identifiant de l'objet à partir de l'objet de données principal dans le magasin de données local. Ensuite, mon serveur en nuage vérifiera l'objectId et le deviceId dans la table AllObjects, et trouvera l'enregistrement à modifier dans la table initiale.
  2. Toutes les modifications doivent être horodatées, afin qu'elles puissent être fusionnées.
  3. L'appareil devra interroger le serveur, sans consommer trop de batterie.
  4. Les dispositifs locaux devront également mettre à jour tout ce qui est conservé en mémoire si/quand des changements sont reçus du serveur.

Y a-t-il autre chose qui m'échappe ici ? Quels types de frameworks dois-je envisager pour rendre cela possible ?

5 votes

Vous ne pouvez pas compter sur la réception des notifications push. L'utilisateur peut simplement les effacer et lorsqu'une deuxième notification arrive, le système d'exploitation rejette la première. De toute façon, les notifications push sont un mauvais moyen de recevoir des mises à jour de synchronisation, car elles interrompent l'utilisateur. L'application devrait lancer la synchronisation dès qu'elle est lancée.

0 votes

OK. Merci pour ces informations. En dehors de l'interrogation constante du serveur et de la vérification des mises à jour au lancement, existe-t-il un moyen pour l'appareil de recevoir des mises à jour ? J'aimerais que cela fonctionne si l'application est ouverte simultanément sur plusieurs appareils.

1 votes

(Je sais que c'est un peu tard, mais au cas où quelqu'un tomberait sur ce sujet et se poserait la question) pour garder plusieurs appareils en synchronisation simultanément, vous pourriez garder une connexion ouverte avec l'autre appareil ou un serveur, et envoyer des messages pour dire à l'autre ou aux autres appareils quand une mise à jour se produit. (par exemple, la façon dont IRC / messagerie instantanée fonctionne)

275voto

chris Points 6000

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

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

J'ai ajouté quatre champs pour faciliter la synchronisation :

  1. sync_status - Ajoutez ce champ uniquement à votre modèle de données principal. 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 qu'il n'y a pas de changement, 1 signifie qu'il est en attente de synchronisation avec le serveur, et 2 signifie que c'est un objet temporaire et qu'il peut être purgé.
  2. est_supprimé - Ajoutez ceci au serveur et au modèle de données principal. L'événement Delete 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 retour. En ayant ce simple drapeau booléen, vous pouvez définir 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. dernière_modification - 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 dans cet enregistrement. Il ne doit jamais être modifié par le client.
  4. guide - Ajouter un identifiant unique au niveau mondial (voir http://en.wikipedia.org/wiki/Globally_unique_identifier ) au serveur et au modèle de données principal. 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 nombre entier incrémentiel sur le serveur, mais nous devons garder à l'esprit que le contenu peut être créé hors ligne et synchronisé ultérieurement. 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 à 1 sur votre objet modèle chaque fois que quelque chose change et doit être synchronisé avec le serveur. Les nouveaux objets modèles doivent générer un GUID.

La synchronisation est une demande unique. La demande contient :

  • L'horodatage MAX last_modified des objets de votre modèle. Cela indique au serveur que vous souhaitez uniquement les modifications postérieures à cette date.
  • Un tableau JSON contenant tous les éléments avec sync_status=1.

Le serveur reçoit la demande et fait cela :

  • 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 dont l'horodatage last_modified est supérieur à l'horodatage envoyé dans la requête. Ce tableau comprendra les objets qu'il vient de recevoir, ce qui sert de confirmation que l'enregistrement a été synchronisé avec succès sur 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 reçoit un sync_status de 0.

J'espère que cela vous aidera. J'ai utilisé les mots "record" et "modèle" de manière interchangeable, mais je pense que vous avez compris l'idée. Bonne chance.

2 votes

Le champ last_modified existe aussi 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.

1 votes

En utilisant des GUID comme PK, n'avez-vous pas rencontré des problèmes de performance liés à l'utilisation de GUID et de PK ? Je cherche à faire la même chose que vous, mais le problème de performance des GUIDs me décourage un peu.

0 votes

Je n'ai pas rencontré de problèmes de performance (pour l'instant). Les champs GUID sont uniques et indexés, donc cela devrait être rapide.

145voto

Massimo Cafaro Points 18759

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 à l'adresse suivante aquí en tant que document pdf.

Il s'agit d'une solution viable et pas si difficile à mettre en œuvre (Dan l'a mise en œuvre dans plusieurs de ses applications), qui recoupe la solution décrite par Chris. Pour une discussion approfondie et théorique 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 aussi bien 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 mise en œuvre correctement.

EDIT :

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

Le cadre Objective-C appelé ZSync et développé par Marcus Zarra a été déprécié, étant donné qu'iCloud semble enfin supporter une synchronisation correcte des données de base.

0 votes

Quelqu'un a un lien mis à jour pour la vidéo ZSync ? Par ailleurs, ZSync est-il toujours maintenu ? Je vois qu'il a été mis à jour pour la dernière fois en 2010.

0 votes

Le dernier commit de ZSync sur github date de septembre 2010, ce qui me laisse penser que Marcus a cessé de le supporter.

1 votes

L'algorithme décrit par Dan Grover est assez bon. Cependant, il ne fonctionnera pas avec un code serveur multithread (donc : il ne sera pas du tout évolutif) 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 ceci.

11voto

radiospiel Points 1342

Si vous êtes toujours à la recherche d'une solution, jetez un coup d'œil au Couchbase mobile. Il fait tout ce que vous voulez. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

3 votes

Cela ne fait ce que vous voulez que si vous pouvez exprimer vos données comme des documents plutôt que des données relationnelles. Il existe des solutions de contournement, mais elles ne sont pas toujours jolies et n'en valent pas la peine.

0 votes

Les documents sont suffisants pour les petites applications

0 votes

@radiospiel Votre lien est cassé

7voto

knagode Points 1176

Comme @Cris, j'ai implémenté une classe pour la synchronisation entre le client et le serveur et j'ai 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 et les fichiers imbriqués, etc.)

Il suffit de dire à la classe quelle entité et quelles colonnes elle doit synchroniser 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 fonctionnel et plus d'instructions ici : github.com/knagode/M3Synchronisation .

0 votes

Sera-t-il possible de modifier la durée du dispositif pour lui donner une valeur anormale ?

5voto

Stan Points 90

Notification à l'utilisateur de la mise à jour des 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 en nuage, lorsque des changements se produisent sur le serveur, les données locales sont modifiées, et vice versa.

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

J'espère que cela pourra vous aider

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X