web-dev-qa-db-fra.com

Comment synchroniser l'iPhone Core Data avec un serveur Web, puis pousser vers d'autres appareils?

Je travaille sur une méthode de synchronisation des données de base stockées dans une application iPhone entre plusieurs périphériques, tels qu'un iPad ou un Mac. Il n'y a pas beaucoup (voire pas du tout) d'infrastructure de synchronisation à utiliser avec Core Data sur iOS. Cependant, j'ai réfléchi au concept suivant:

  1. Une modification est apportée au magasin de données principal local et est enregistrée. (a) Si le périphérique est en ligne, il tente d'envoyer le jeu de modifications au serveur, y compris l'ID de périphérique du périphérique qui a envoyé le jeu de modifications. (b) Si le jeu de modifications ne parvient pas au serveur ou si le périphérique n'est pas en ligne, l'application ajoutera le jeu de modifications à une file d'attente à envoyer lorsqu'elle sera mise 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. Une fois qu'un jeu de modifications (ou une file d'attente de jeux de modifications) a été fusionné sur le serveur cloud, le serveur transmet tous ces jeux de modifications aux autres périphériques enregistrés auprès du serveur à l'aide d'une sorte de système de scrutation. (J'ai pensé utiliser les services Push d'Apple, mais apparemment, selon les commentaires, ce n'est pas un système utilisable.)

Y a-t-il quelque chose d'extraordinaire auquel je dois penser? J'ai examiné les structures REST telles que ObjectiveResource , ressource principale et RestfulCoreData . Bien sûr, ils fonctionnent tous avec Ruby sur Rails, auquel je ne suis pas lié, mais c'est un endroit pour commencer. Les principales exigences de ma solution sont les suivantes:

  1. Toutes les modifications doivent être envoyées en arrière-plan sans suspendre le thread principal.
  2. Il devrait utiliser le moins de bande passante possible.

J'ai réfléchi à plusieurs défis:

  1. Assurez-vous que les ID d'objet des différents magasins de données sur différents périphériques sont attachés au serveur. C'est-à-dire que j'aurai une table d'ID d'objet et d'ID de périphérique, liés par 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 toute la base de données], Datafield1, Datafield2), le champ ObjectId référencera une autre table, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Ensuite, lorsque le périphérique émet un jeu 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 de nuage vérifiera les identifiants objectId et device de la table AllObjects, puis recherchera l’enregistrement à modifier dans la table initiale.
  2. Tous les changements doivent être horodatés, afin de pouvoir être fusionnés.
  3. L’appareil devra interroger le serveur sans utiliser trop de batterie.
  4. Les périphériques locaux devront également mettre à jour tout ce qui est en mémoire si/quand les modifications sont reçues du serveur.

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

291
Jason

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 format PDF.

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

Synchronisation de fichier avec des paires de temps vectorielles

qui s’applique aussi bien aux données de base avec quelques modifications évidentes. Ceci fournit une stratégie de synchronisation beaucoup plus robuste et fiable, mais nécessite plus d'effort pour être mis en œuvre correctement.

MODIFIER:

Il semble que le fichier pdf de Grover n'est plus disponible (lien brisé, mars 2015). UPDATE: le lien est disponible via la machine de retour ici

Le framework Objective-C appelé ZSync et développé par Marcus Zarra est déconseillé, car iCloud semble enfin prendre en charge la synchronisation correcte des données de base.

141
Massimo Cafaro

J'ai fait quelque chose de similaire à ce que vous essayez de faire. Laissez-moi 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 de base de données) sur le serveur. Vous souhaitez simplement que le contenu du serveur soit synchronisé avec les clients, mais les clients peuvent également modifier et ajouter des données. Si j'ai bien compris, alors continuez à lire.

J'ai ajouté quatre champs d'aide à 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 pas de changement, 1 signifie qu'il est en file d'attente pour être synchronisé sur le serveur et 2 signifie que c'est un objet temporaire et qu'il 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 supprimer une ligne de la base de données ou de votre modèle client, car il ne vous reste plus rien à synchroniser. En ayant ce simple indicateur booléen, vous pouvez définir is_deleted sur 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 automatiquement être mis à jour avec la date et l'heure actuelles par le serveur chaque fois que quelque chose change sur cet enregistrement. Il ne devrait jamais être modifié par le client.
  4. guid - Ajoutez un identifiant global unique (voir http://en.wikipedia.org/wiki/Globally_unique_identifier ) vers le serveur et le 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 entier incrémentant sur le serveur, mais nous devons garder à l’esprit que le contenu peut être créé hors connexion et synchronisé ultérieurement. Le GUID nous permet de créer une clé en étant hors ligne.

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

La synchronisation est une requête unique. La demande contient:

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

Le serveur obtient 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 dont l'horodatage last_modified est 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é 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 est défini sur un sync_status égal à 0.

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

270
chris

Si vous êtes toujours à la recherche d'un moyen d'aller plus loin, jetez un coup d'œil au téléphone mobile Couchbase. Cela fait essentiellement tout ce que vous voulez. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

11
radiospiel

Comme @Cris, j’ai implémenté class pour la synchronisation entre client et serveur et résolu jusqu’à présent tous les problèmes connus (envoi/réception de données vers/depuis le serveur, conflits de fusion fondés sur des horodatages, suppression des entrées en double dans des conditions de réseau peu fiables, synchronisation des données imbriquées, etc. fichiers etc ..)

Vous venez d'indiquer à la classe quelle entité et quelles colonnes doit-elle être synchronisée 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 pratique et d’autres instructions ici: github.com/knagode/M3Synchronization .

7
knagode

Je pense qu'une bonne solution au problème de GUID est "système d'identification distribuée". Je ne sais pas quel est le terme correct, mais je pense que c'est ainsi que les documents 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 identifiants. Chaque fois qu'une synchronisation est effectuée, la première chose vérifiée est "Combien d'ID me reste-t-il sur ce client?" Si le client est à l'état bas, il demande au serveur un nouveau bloc d'identifiants. Le client utilise ensuite les identifiants de cette plage pour les nouveaux enregistrements. Cela fonctionne très bien pour la plupart des besoins, si vous pouvez affecter un bloc suffisamment volumineux pour qu'il ne soit "jamais" épuisé avant la prochaine synchronisation, mais pas si grand que le serveur ne s'épuise pas dans le temps. Si le client s'épuise, le traitement peut être assez simple, dites simplement à l'utilisateur "désolé, vous ne pouvez pas ajouter plus d'éléments jusqu'à ce que vous synchronisiez" ... s'ils ajoutent autant d'éléments, ils ne devraient pas se synchroniser pour éviter les données obsolètes. questions quand même?

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 identifiant standard (128 bits par rapport à 32 bits). Vous avez généralement des index par ID et vous conservez souvent les numéros d’identification en mémoire. Il est donc important de les garder petits.

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

5
eselk

Je viens de publier la première version de ma nouvelle API de synchronisation Core Data Cloud, appelée SynCloud. SynCloud présente de nombreuses 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 la création de données relationnelles multi-tables.

Pour en savoir plus, visitez le site http://www.syncloudapi.com

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

5
logan

Avis à l'utilisateur de 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 inversement.

Je pense donc que la partie la plus difficile consiste à estimer les données de quel côté est invalide.

J'espère que cela peut vous aider

5
Stan

Tout d’abord, vous devez repenser le nombre de données, de tables et de relations que vous aurez. Dans ma solution, j'ai implémenté la synchronisation via des fichiers Dropbox. J'observe les changements dans le fichier MOC principal et enregistre ces données dans des fichiers (chaque ligne est enregistrée sous json gzipped). Si une connexion Internet fonctionne, je vérifie si des modifications ont été apportées à Dropbox (Dropbox me donne les modifications de type delta), je les télécharge et les fusionne (les dernières victoires), puis je place les fichiers modifiés. Avant la synchronisation, je mets le fichier de verrouillage sur Dropbox pour empêcher les autres clients de synchroniser des données incomplètes. Lors du téléchargement des modifications, il est prudent de ne télécharger que des données partielles (par exemple, une connexion Internet perdue). Lorsque le téléchargement est terminé (complètement ou partiellement), les fichiers sont chargés dans Core Data. Lorsqu'il y a des relations non résolues (tous les fichiers ne sont pas téléchargés), le chargement des fichiers est arrêté et le téléchargement est terminé plus tard. Les relations sont stockées uniquement en tant que GUID, ce qui me permet de vérifier facilement quels fichiers doivent être chargés pour garantir l’intégrité complète des données. La synchronisation commence après la modification des 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 de ces modifications, afin qu'ils puissent se synchroniser plus rapidement. Chaque entité synchronisée a la 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 la comparer lorsque le delta de Dropbox réinitialise son état). Les fichiers contiennent également le nom de l'entité, l'état (supprimé/non supprimé), guid (identique au nom du fichier), la révision de la base de données (pour détecter les migrations de données ou éviter la synchronisation avec des versions jamais app) 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é/valeur comme REST service Web que je souhaite créer ultérieurement, mais je n’ai pas le temps de le faire :) Pour l’instant, ma solution est plus fiable que iCloud et , qui est très important, j’ai le plein contrôle sur la façon dont cela fonctionne (principalement parce que c’est mon propre code).

Une autre solution consiste à enregistrer les modifications MOC en tant que transactions: le nombre de fichiers échangés avec le serveur sera beaucoup moins important, mais il sera plus difficile de charger 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 une approche similaire, par exemple TICoreDataSync .

-- MISE À JOUR

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

2
thom_ek