Come sincronizzare iPhone Core Data con il web server e quindi spingere su altri dispositivi? [chiuso]


293

Ho lavorato su un metodo per sincronizzare i dati principali memorizzati in un'applicazione iPhone tra più dispositivi, come un iPad o un Mac. Non ci sono molti (se ve ne sono) framework di sincronizzazione da utilizzare con Core Data su iOS. Tuttavia, ho pensato al seguente concetto:

  1. Viene apportata una modifica all'archivio dati core locale e la modifica viene salvata. (a) Se il dispositivo è online, tenta di inviare il changeset al server, incluso l'ID del dispositivo che ha inviato il changeset. (b) Se il changeset non raggiunge il server o se il dispositivo non è online, l'app aggiungerà il set di modifiche a una coda da inviare quando sarà online.
  2. Il server, seduto nel cloud, unisce i set di modifiche specifici che riceve con il suo database principale.
  3. Dopo che un set di modifiche (o una coda di set di modifiche) viene unito sul server cloud, il server invia tutti questi set di modifiche agli altri dispositivi registrati con il server utilizzando una sorta di sistema di polling. (Ho pensato di utilizzare i servizi Push di Apple, ma a quanto pare secondo i commenti questo non è un sistema funzionante.)

C'è qualcosa di speciale a cui devo pensare? Ho esaminato i framework REST come ObjectiveResource , Core Resource e RestfulCoreData . Ovviamente, stanno tutti lavorando con Ruby on Rails, a cui non sono legato, ma è un punto di partenza. I requisiti principali che ho per la mia soluzione sono:

  1. Eventuali modifiche devono essere inviate in background senza mettere in pausa il thread principale.
  2. Dovrebbe utilizzare la minore larghezza di banda possibile.

Ho pensato a una serie di sfide:

  1. Assicurarsi che gli ID oggetto per i diversi archivi di dati su dispositivi diversi siano collegati sul server. Vale a dire, avrò una tabella di ID oggetto e ID dispositivo, che sono collegati tramite un riferimento all'oggetto archiviato nel database. Avrò un record (DatabaseId [unico per questa tabella], ObjectId [unico per l'elemento nell'intero database], Datafield1, Datafield2), il campo ObjectId farà riferimento a un'altra tabella, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Quindi, quando il dispositivo esegue il push up di un set di modifiche, passerà lungo l'ID del dispositivo e l'oggettoId dall'oggetto dati principale nell'archivio dati locale. Quindi il mio server cloud verificherà con objectId e ID dispositivo nella tabella AllObjects e troverà il record da modificare nella tabella iniziale.
  2. Tutti i cambiamenti devono essere marcati con il timestamp, in modo che possano essere uniti.
  3. Il dispositivo dovrà eseguire il polling del server, senza consumare troppa batteria.
  4. I dispositivi locali dovranno inoltre aggiornare tutto ciò che è in memoria se / quando le modifiche vengono ricevute dal server.

C'è qualcos'altro che mi manca qui? Quali tipi di framework dovrei guardare per renderlo possibile?


5
Non puoi fare affidamento sulla ricezione di notifiche push. L'utente può semplicemente toccarli e quando arriva una seconda notifica, il sistema operativo getta via il primo. Le notifiche push IMO sono comunque un cattivo modo di ricevere gli aggiornamenti di sincronizzazione perché interrompono l'utente. L'app dovrebbe avviare la sincronizzazione ogni volta che viene avviata.
Ole Begemann,

OK. Grazie per le informazioni: oltre al polling costante del server e alla verifica degli aggiornamenti all'avvio, esiste un modo per ottenere aggiornamenti dal dispositivo? Sono interessato a farlo funzionare se l'app è aperta su più dispositivi contemporaneamente.
Jason,

1
(Conosco un po 'tardi, ma in caso qualcuno si imbatti in questo e si chieda anche) per mantenere sincronizzati più dispositivi contemporaneamente, potresti mantenere una connessione aperta con l'altro dispositivo o un server e inviare messaggi per dire all'altro dispositivo ) quando si verifica un aggiornamento. (ad esempio il modo in cui funziona IRC / messaggistica istantanea)
Dan2552

1
@ Dan2552: ciò che descrivi è noto come [polling lungo] [ en.wikipedia.org/wiki/… ed è un'ottima idea, tuttavia le connessioni aperte consumano molta batteria e larghezza di banda su un dispositivo mobile.
johndodo,

1
Ecco un buon tutorial di Ray Wenderlich su come sincronizzare i dati tra la tua app e il servizio web: raywenderlich.com/15916/…
JRG-Developer

Risposte:


144

Suggerisco di leggere attentamente e attuare la strategia di sincronizzazione discussa da Dan Grover alla conferenza di iPhone 2009, disponibile qui come documento pdf.

Questa è una soluzione praticabile e non è così difficile da implementare (Dan l'ha implementata in molte delle sue applicazioni), sovrapponendosi alla soluzione descritta da Chris. Per una discussione teorica approfondita sulla sincronizzazione, vedi l'articolo di Russ Cox (MIT) e William Josephson (Princeton):

Sincronizzazione dei file con coppie temporali vettoriali

che si applica ugualmente bene ai dati core con alcune ovvie modifiche. Ciò fornisce una strategia di sincronizzazione complessivamente molto più solida e affidabile, ma richiede uno sforzo maggiore per essere implementata correttamente.

MODIFICARE:

Sembra che il file pdf di Grover non sia più disponibile (link non funzionante, marzo 2015). AGGIORNAMENTO: il link è disponibile tramite la Way Back Machine qui

Il framework Objective-C chiamato ZSync e sviluppato da Marcus Zarra è stato deprecato, dato che iCloud sembra finalmente supportare la corretta sincronizzazione dei dati di base.


Qualcuno ha un link aggiornato per il video ZSync? Inoltre, ZSync è ancora mantenuto? Vedo che è stato aggiornato l'ultima volta nel 2010.
Jeremie Weldin,

L'ultimo commit di ZSync su github è stato nel settembre 2010, il che mi porta a credere che Marcus abbia smesso di supportarlo.
Daishable Dave,

1
L'algoritmo descritto da Dan Grover è abbastanza buono. Tuttavia, non funzionerà con un codice server multi-thread (quindi: questo non si ridimensionerà affatto) poiché non c'è modo di assicurarsi che un client non perda un aggiornamento quando viene utilizzato il tempo per verificare la presenza di nuovi aggiornamenti . Per favore, correggimi se sbaglio - vorrei uccidere per vedere un'implementazione funzionante di questo.
masi,

1
@Patt, ti ho appena inviato il file pdf, come richiesto. Saluti, Massimo Cafaro.
Massimo Cafaro,

3
Le diapositive PDF mancanti di sincronizzazione multipiattaforma di Dan Grover sono accessibili tramite la Wayback Machine.
Matthew Kairys,

272

Ho fatto qualcosa di simile a quello che stai cercando di fare. Lascia che ti dica cosa ho imparato e come l'ho fatto.

Presumo che tu abbia una relazione uno a uno tra l'oggetto Core Data e il modello (o lo schema db) sul server. Volete semplicemente mantenere i contenuti del server sincronizzati con i client, ma i client possono anche modificare e aggiungere dati. Se ho capito bene, continua a leggere.

Ho aggiunto quattro campi per facilitare la sincronizzazione:

  1. sync_status : aggiungi questo campo solo al modello di dati di base. Viene utilizzato dall'app per determinare se è presente una modifica in sospeso sull'elemento. Uso i seguenti codici: 0 indica nessuna modifica, 1 indica che è in coda per essere sincronizzato con il server e 2 indica che è un oggetto temporaneo e può essere eliminato.
  2. is_deleted - Aggiungilo al modello di dati del server e del core. L'evento di eliminazione non dovrebbe effettivamente eliminare una riga dal database o dal modello client in quanto non consente di eseguire nuovamente la sincronizzazione. Avendo questo semplice flag booleano, puoi impostare is_deleted su 1, sincronizzarlo e tutti saranno felici. È inoltre necessario modificare il codice sul server e sul client per eseguire query sugli elementi non eliminati con "is_deleted = 0".
  3. Ultima modifica - Aggiungilo al modello di dati del server e del core. Questo campo dovrebbe essere automaticamente aggiornato con la data e l'ora correnti dal server ogni volta che qualcosa cambia in quel record. Non dovrebbe mai essere modificato dal cliente.
  4. guid - Aggiungi un campo univoco globale (vedi http://en.wikipedia.org/wiki/Globally_unique_identifier ) al server e al modello di dati di base. Questo campo diventa la chiave primaria e diventa importante quando si creano nuovi record sul client. Normalmente la tua chiave primaria è un numero intero incrementale sul server, ma dobbiamo tenere presente che il contenuto potrebbe essere creato offline e sincronizzato in un secondo momento. Il GUID ci consente di creare una chiave mentre si è offline.

Sul client, aggiungi il codice per impostare sync_status su 1 sull'oggetto modello ogni volta che qualcosa cambia e deve essere sincronizzato con il server. I nuovi oggetti modello devono generare un GUID.

La sincronizzazione è una singola richiesta. La richiesta contiene:

  • Il timestamp MAX last_modified degli oggetti modello. Ciò indica al server che si desidera modificare solo dopo questo timestamp.
  • Un array JSON contenente tutti gli elementi con sync_status = 1.

Il server riceve la richiesta e lo fa:

  • Prende il contenuto dall'array JSON e modifica o aggiunge i record in esso contenuti. Il campo last_modified viene automaticamente aggiornato.
  • Il server restituisce un array JSON contenente tutti gli oggetti con un timestamp last_modified maggiore del timestamp inviato nella richiesta. Ciò includerà gli oggetti che ha appena ricevuto, che serve da riconoscimento del fatto che il record è stato correttamente sincronizzato con il server.

L'app riceve la risposta e lo fa:

  • Prende il contenuto dall'array JSON e modifica o aggiunge i record in esso contenuti. Ogni record viene impostato un sync_status di 0.

Spero che aiuti. Ho usato la parola record e il modello in modo intercambiabile, ma penso che tu abbia avuto l'idea. In bocca al lupo.


2
Il campo last_modified esiste anche nel database locale, ma non è aggiornato dall'orologio dell'iPhone. Viene impostato dal server e sincronizzato indietro. La data MAX (ultima_modificata) è ciò che l'app invia al server per dirle di rispedire tutto modificato dopo quella data.
chris,

3
Un valore globale sul client potrebbe sostituire MAX(last_modified), ma sarebbe ridondante poiché è MAX(last_modified)sufficiente. Il sync_statusha un altro ruolo. Come ho scritto prima, MAX(last_modified)determina cosa deve essere sincronizzato dal server, mentre sync_statusdetermina cosa deve essere sincronizzato con il server.
chris,

2
@Flex_Addicted Grazie. Sì, è necessario replicare i campi per ciascuna entità che si desidera sincronizzare. Tuttavia, è necessario prestare maggiore attenzione durante la sincronizzazione di un modello con una relazione (ad esempio, 1 a molti).
chris,

2
@BenPackard - Hai ragione. L'approccio non risolve i conflitti, quindi vincerà l'ultimo client. Non ho dovuto occuparmene nelle mie app poiché i record sono stati modificati da un singolo utente. Sarei curioso di sapere come risolverlo.
chris

2
Ciao @noilly, considera il caso seguente: apporti modifiche a un oggetto locale e devi sincronizzarlo di nuovo con il server. La sincronizzazione può avvenire solo ore o giorni dopo (ad esempio se sei stato offline per un po ') e in quel momento l'app potrebbe essere stata arrestata e riavviata alcune volte. In questo caso i metodi su NSManagedObjectContext non sarebbero di grande aiuto.
chris,

11

Se stai ancora cercando un modo per andare, guarda nel cellulare Couchbase. Questo in pratica fa tutto ciò che vuoi. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )


3
Questo fa solo quello che vuoi se puoi esprimere i tuoi dati come documenti piuttosto che come dati relazionali. Ci sono soluzioni alternative, ma non sono sempre belle o ne valgono la pena.
Jeremie Weldin,

i documenti sono sufficienti per piccole applicazioni
Hai Feng Kao,

@radiospiel Il tuo link non funziona
Mick,

Ciò aggiungerà anche una dipendenza che il back-end deve essere scritto in Couchbase DB. Anche io ho iniziato con l'idea di NOSQL per la sincronizzazione, ma non posso limitare il mio backend ad essere NOSQL poiché MS SQL è in esecuzione in backend.
Martedì

@Mick: sembra funzionare di nuovo (o qualcuno ha corretto il collegamento? Grazie)
radiospiel

7

Simile a @Cris, ho implementato la classe per la sincronizzazione tra client e server e finora ho risolto tutti i problemi noti (invio / ricezione di dati al / dal server, unione di conflitti basati su timestamp, rimozione di voci duplicate in condizioni di rete inaffidabili, sincronizzazione dei dati nidificati e file ecc.)

Devi solo dire alla classe quale entità e quali colonne deve sincronizzare e dove si trova il tuo server.

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

Puoi trovare la fonte, l'esempio funzionante e altre istruzioni qui: github.com/knagode/M3Sincronizzazione .


Andrà bene se cambiamo l'ora del dispositivo a un valore anomalo?
Golden

5

Avviso all'utente di aggiornare i dati tramite notifica push. Utilizzare un thread in background nell'app per verificare i dati locali e i dati sul server cloud, mentre la modifica avviene sul server, modificare i dati locali, viceversa.

Quindi penso che la parte più difficile sia stimare i dati in cui la parte non è valida.

Spero che questo possa aiutarti


5

Ho appena pubblicato la prima versione della mia nuova API Core Data Cloud Syncing, nota come SynCloud. SynCloud ha molte differenze con iCloud perché consente l'interfaccia di sincronizzazione multiutente. È anche diverso dalle altre API di sincronizzazione perché consente dati relazionali a più tabelle.

Ulteriori informazioni su http://www.syncloudapi.com

Costruito con iOS 6 SDK, è molto aggiornato dal 27/09/2012.


5
Benvenuto in Stack Overflow! Grazie per aver pubblicato la tua risposta! Assicurati di leggere attentamente le FAQ sull'autopromozione .
Andrew Barber,

5

Penso che una buona soluzione al problema GUID sia il "sistema ID distribuito". Non sono sicuro di quale sia il termine corretto, ma penso che sia quello che i documenti del server MS SQL usavano per chiamarlo (SQL utilizza / usava questo metodo per database distribuiti / sincronizzati). È abbastanza semplice:

Il server assegna tutti gli ID. Ogni volta che viene eseguita una sincronizzazione, la prima cosa che viene controllata è "Quanti ID sono rimasti su questo client?" Se il client si sta esaurendo, chiede al server un nuovo blocco di ID. Il client utilizza quindi gli ID in quell'intervallo per i nuovi record. Funziona alla grande per la maggior parte delle esigenze, se è possibile assegnare un blocco abbastanza grande da non "mai" esaurirsi prima della sincronizzazione successiva, ma non così grande che il server si esaurisca nel tempo. Se il client si esaurisce, la gestione può essere piuttosto semplice, basta dire all'utente "mi dispiace non poter aggiungere più elementi fino alla sincronizzazione" ... se stanno aggiungendo tanti elementi, non dovrebbero sincronizzarsi per evitare dati non aggiornati problemi comunque?

Penso che questo sia superiore all'utilizzo di GUID casuali perché i GUID casuali non sono sicuri al 100% e di solito devono essere molto più lunghi di un ID standard (128 bit contro 32 bit). Di solito hai indici per ID e spesso mantieni i numeri ID in memoria, quindi è importante tenerli piccoli.

Non volevo davvero postare come risposta, ma non so che qualcuno lo vedrebbe come un commento, e penso che sia importante per questo argomento e non incluso in altre risposte.


2

Per prima cosa dovresti ripensare quanti dati, tabelle e relazioni avrai. Nella mia soluzione ho implementato la sincronizzazione tramite i file Dropbox. Osservo i cambiamenti nel MOC principale e salvo questi dati su file (ogni riga viene salvata come json gzip). Se c'è una connessione Internet funzionante, controllo se ci sono cambiamenti su Dropbox (Dropbox mi dà delta modifiche), scaricarli e unirli (ultime vittorie), e infine mettere i file modificati. Prima della sincronizzazione ho inserito il file di blocco su Dropbox per impedire ad altri client di sincronizzare dati incompleti. Durante il download delle modifiche è sicuro che vengono scaricati solo dati parziali (ad es. Perdita della connessione a Internet). Al termine del download (completo o parziale), inizia a caricare i file in Core Data. Quando ci sono relazioni irrisolte (non tutti i file vengono scaricati), il caricamento si interrompe e tenta di terminare il download in un secondo momento. Le relazioni sono memorizzate solo come GUID, quindi posso facilmente verificare quali file caricare per avere la piena integrità dei dati. La sincronizzazione inizia dopo che sono state apportate modifiche ai dati principali. Se non ci sono modifiche, controlla le modifiche su Dropbox ogni pochi minuti e all'avvio dell'app. Inoltre, quando le modifiche vengono inviate al server, invio una trasmissione ad altri dispositivi per informarli delle modifiche, in modo che possano sincronizzarsi più rapidamente. Ogni entità sincronizzata ha la proprietà GUID (guid viene utilizzato anche come nome file per lo scambio di file). Ho anche il database di sincronizzazione in cui memorizzo la revisione di Dropbox di ciascun file (posso confrontarlo quando il delta di Dropbox ripristina il suo stato). I file contengono anche il nome dell'entità, lo stato (eliminato / non eliminato), guid (lo stesso del nome file), la revisione del database (per rilevare migrazioni di dati o per evitare la sincronizzazione con versioni mai app) e, naturalmente, i dati (se la riga non viene eliminata). così posso facilmente controllare quali file caricare per avere la piena integrità dei dati. La sincronizzazione inizia dopo che sono state apportate modifiche ai dati principali. Se non ci sono modifiche, controlla le modifiche su Dropbox ogni pochi minuti e all'avvio dell'app. Inoltre, quando le modifiche vengono inviate al server, invio una trasmissione ad altri dispositivi per informarli delle modifiche, in modo che possano sincronizzarsi più rapidamente. Ogni entità sincronizzata ha la proprietà GUID (guid viene utilizzato anche come nome file per lo scambio di file). Ho anche il database di sincronizzazione in cui memorizzo la revisione di Dropbox di ciascun file (posso confrontarlo quando il delta di Dropbox ripristina il suo stato). I file contengono anche il nome dell'entità, lo stato (eliminato / non eliminato), guid (lo stesso del nome file), la revisione del database (per rilevare migrazioni di dati o per evitare la sincronizzazione con versioni mai app) e, naturalmente, i dati (se la riga non viene eliminata). così posso facilmente controllare quali file caricare per avere la piena integrità dei dati. La sincronizzazione inizia dopo che sono state apportate modifiche ai dati principali. Se non ci sono modifiche, controlla le modifiche su Dropbox ogni pochi minuti e all'avvio dell'app. Inoltre, quando le modifiche vengono inviate al server, invio una trasmissione ad altri dispositivi per informarli delle modifiche, in modo che possano sincronizzarsi più rapidamente. Ogni entità sincronizzata ha la proprietà GUID (guid viene utilizzato anche come nome file per lo scambio di file). Ho anche il database di sincronizzazione in cui memorizzo la revisione di Dropbox di ciascun file (posso confrontarlo quando il delta di Dropbox ripristina il suo stato). I file contengono anche il nome dell'entità, lo stato (eliminato / non eliminato), guid (lo stesso del nome file), la revisione del database (per rilevare migrazioni di dati o per evitare la sincronizzazione con versioni mai app) e, naturalmente, i dati (se la riga non viene eliminata).

Questa soluzione funziona per migliaia di file e circa 30 entità. Invece di Dropbox potrei usare l'archivio chiave / valore come servizio web REST che voglio fare in seguito, ma non ho tempo per questo :) Per ora, secondo me, la mia soluzione è più affidabile di iCloud e, che è molto importante, Ho il pieno controllo su come funziona (principalmente perché è il mio codice).

Un'altra soluzione è salvare le modifiche al MOC come transazioni: ci saranno molti meno file scambiati con il server, ma è più difficile eseguire il caricamento iniziale nell'ordine corretto in dati core vuoti. iCloud funziona in questo modo e anche altre soluzioni di sincronizzazione hanno un approccio simile, ad esempio TICoreDataSync .

-- AGGIORNARE

Dopo un po 'sono migrato su Ensembles : consiglio questa soluzione per reinventare la ruota.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.