Come sincronizzare CoreData e un servizio Web REST in modo asincrono e allo stesso tempo propagare correttamente eventuali errori REST nell'interfaccia utente


85

Ehi, sto lavorando al livello del modello per la nostra app qui.

Alcuni dei requisiti sono come questo:

  1. Dovrebbe funzionare su iPhone OS 3.0+.
  2. La fonte dei nostri dati è un'applicazione RESTful Rails.
  3. Dovremmo memorizzare nella cache i dati localmente utilizzando Core Data.
  4. Il codice client (i nostri controller dell'interfaccia utente) dovrebbe avere la minima conoscenza possibile di qualsiasi cosa di rete e dovrebbe interrogare / aggiornare il modello con la Core Data API.

Ho controllato la WWDC10 Session 117 sulla creazione di un'esperienza utente basata su server, ho passato un po 'di tempo a controllare Objective Resource , Core Resource e RestfulCoreData framework .

Il framework Objective Resource non parla da solo con Core Data ed è semplicemente un'implementazione del client REST. Core Resource e RestfulCoreData presumono che tu parli con Core Data nel tuo codice e risolvono tutti i dadi e i bulloni in background sul livello del modello.

Finora tutto sembra a posto e inizialmente pensavo che Core Resource o RestfulCoreData coprissero tutti i requisiti di cui sopra, ma ... Ci sono un paio di cose che nessuno di loro sembra risolvere correttamente:

  1. Il thread principale non deve essere bloccato durante il salvataggio degli aggiornamenti locali sul server.
  2. Se l'operazione di salvataggio non riesce, l'errore deve essere propagato all'interfaccia utente e nessuna modifica deve essere salvata nell'archivio dati di base locale.

Core Resource emette tutte le sue richieste al server quando si richiama - (BOOL)save:(NSError **)erroril contesto dell'oggetto gestito e quindi è in grado di fornire un'istanza NSError corretta delle richieste sottostanti al server in qualche modo non riuscita. Ma blocca il thread chiamante fino al termine dell'operazione di salvataggio. FALLIRE.

RestfulCoreData mantiene -save:intatte le chiamate e non introduce alcun tempo di attesa aggiuntivo per il thread del client. Si limita a controllare NSManagedObjectContextDidSaveNotificatione quindi invia le richieste corrispondenti al server nel gestore delle notifiche. Ma in questo modo la -save:chiamata viene sempre completata correttamente (beh, dato che Core Data va bene con le modifiche salvate) e il codice client che effettivamente lo ha chiamato non ha modo di sapere che il salvataggio potrebbe non essere riuscito a propagarsi al server a causa di alcuni 404o421 o qualsiasi altra cosa si è verificato un errore lato server. E ancora di più, l'archiviazione locale diventa per aggiornare i dati, ma il server non viene mai a conoscenza delle modifiche. FALLIRE.

Quindi, sto cercando una possibile soluzione / pratiche comuni per affrontare tutti questi problemi:

  1. Non voglio che il thread chiamante si blocchi su ogni -save:chiamata mentre si verificano le richieste di rete.
  2. Voglio in qualche modo ricevere notifiche nell'interfaccia utente che alcune operazioni di sincronizzazione sono andate male.
  3. Voglio che anche il salvataggio dei dati di base effettivi fallisca se le richieste del server falliscono.

Qualche idea?


1
Wow, non hai idea di quanti problemi mi hai salvato ponendomi questa domanda. Al momento ho implementato la mia app per far sì che l'utente attenda i dati ogni volta che effettuo una chiamata (anche se a un servizio web .NET). Ho pensato a un modo per renderlo asincrono ma non sono riuscito a capire come farlo. Grazie per tutte le risorse che hai fornito!
Tejaswi Yerukalapudi,

Ottima domanda, grazie.
Justin

Il collegamento alla risorsa principale è interrotto, qualcuno sa dove è ospitato ora?

Core Resource è ancora ospitata su GitHub qui: github.com/mikelaurence/CoreResource
eploko

E il sito originale può essere trovato anche su gitHub: github.com/mikelaurence/coreresource.org
eploko

Risposte:


26

Dovresti davvero dare un'occhiata a RestKit ( http://restkit.org ) per questo caso d'uso. È progettato per risolvere i problemi di modellazione e sincronizzazione di risorse JSON remote su una cache supportata da Core Data locale. Supporta una modalità offline per lavorare interamente dalla cache quando non è disponibile una rete. Tutta la sincronizzazione avviene su un thread in background (accesso alla rete, analisi del payload e unione del contesto dell'oggetto gestito) ed è disponibile un ricco set di metodi delegati in modo da poter sapere cosa sta succedendo.


18

Ci sono tre componenti di base:

  1. L'azione dell'interfaccia utente e il mantenimento della modifica a CoreData
  2. Persistendo quella modifica fino al server
  3. Aggiornamento dell'interfaccia utente con la risposta del server

Un NSOperation + NSOperationQueue aiuterà a mantenere ordinate le richieste di rete. Un protocollo delegato aiuterà le classi dell'interfaccia utente a capire in che stato si trovano le richieste di rete, qualcosa come:

@protocol NetworkOperationDelegate
  - (void)operation:(NSOperation *)op willSendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op didSuccessfullySendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op encounteredAnError:(NSError *)error afterSendingRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
@end

Il formato del protocollo dipenderà ovviamente dal tuo caso d'uso specifico, ma essenzialmente ciò che stai creando è un meccanismo mediante il quale le modifiche possono essere "inviate" al tuo server.

Successivamente c'è il ciclo dell'interfaccia utente da considerare, per mantenere pulito il codice sarebbe bello chiamare save: e fare in modo che le modifiche vengano automaticamente inviate al server. È possibile utilizzare le notifiche NSManagedObjectContextDidSave per questo.

- (void)managedObjectContextDidSave:(NSNotification *)saveNotification {
  NSArray *inserted = [[saveNotification userInfo] valueForKey:NSInsertedObjects];
  for (NSManagedObject *obj in inserted) {
    //create a new NSOperation for this entity which will invoke the appropraite rest api
    //add to operation queue
  }

  //do the same thing for deleted and updated objects
}

L'overhead computazionale per l'inserimento delle operazioni di rete dovrebbe essere piuttosto basso, tuttavia se crea un notevole ritardo sull'interfaccia utente potresti semplicemente estrarre gli ID entità dalla notifica di salvataggio e creare le operazioni su un thread in background.

Se la tua API REST supporta il batch, potresti persino inviare l'intero array contemporaneamente e quindi notificare all'interfaccia utente che più entità sono state sincronizzate.

L'unico problema che prevedo e per il quale non esiste una soluzione "reale" è che l'utente non vorrà aspettare che le proprie modifiche vengano inviate al server per poter apportare ulteriori modifiche. L'unico buon paradigma in cui mi sono imbattuto è che consenti all'utente di continuare a modificare gli oggetti e raggruppare le modifiche insieme quando appropriato, ovvero non spingere su ogni notifica di salvataggio.


2

Questo diventa un problema di sincronizzazione e non facile da risolvere. Ecco cosa farei: Nell'interfaccia utente del tuo iPhone usa un contesto e quindi usando un altro contesto (e un altro thread) scarica i dati dal tuo servizio web. Una volta che è tutto lì, passa attraverso i processi di sincronizzazione / importazione consigliati di seguito e quindi aggiorna la tua interfaccia utente dopo che tutto è stato importato correttamente. Se le cose vanno male durante l'accesso alla rete, è sufficiente ripristinare le modifiche nel contesto non UI. È un sacco di lavoro, ma penso che sia il modo migliore per affrontarlo.

Dati principali: importazione efficiente dei dati

Dati fondamentali: gestione del cambiamento

Dati core: multi-threading con dati core


0

È necessaria una funzione di callback che verrà eseguita sull'altro thread (quello in cui avviene l'interazione effettiva del server) e quindi inserire il codice di risultato / le informazioni sull'errore in un dato semi-globale che verrà periodicamente controllato dal thread dell'interfaccia utente. Assicurati che il wirting del numero che funge da flag sia atomico o avrai una condizione di gara - dì se la tua risposta di errore è di 32 byte hai bisogno di un int (che dovrebbe avere un accesso atomico) e poi tienilo int nello stato off / false / not-ready fino a quando il blocco di dati più grande non è stato scritto e solo allora scrivi "true" per attivare l'interruttore per così dire.

Per il salvataggio correlato sul lato client devi solo conservare quei dati e non salvarli fino a quando non ottieni OK dal server o assicurati di avere una certa opzione di rollback - ad esempio un modo per eliminare è il server non riuscito.

Fai attenzione che non sarà mai sicuro al 100% a meno che non esegui la procedura di commit in 2 fasi completa (il salvataggio o l'eliminazione del client può fallire dopo il segnale dal server del server) ma ti costerà almeno 2 viaggi al server ( potrebbe costarti 4 se la tua unica opzione di rollback è l'eliminazione).

Idealmente, dovresti eseguire l'intera versione di blocco dell'operazione su un thread separato, ma per questo avresti bisogno di 4.0.

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.