So synchronisieren Sie CoreData und einen REST-Webdienst asynchron und verbreiten gleichzeitig REST-Fehler ordnungsgemäß in der Benutzeroberfläche

84

Hey, ich arbeite hier an der Modellebene für unsere App.

Einige der Anforderungen lauten wie folgt:

  1. Es sollte unter iPhone OS 3.0+ funktionieren.
  2. Die Quelle unserer Daten ist eine RESTful Rails-Anwendung.
  3. Wir sollten die Daten lokal mit Core Data zwischenspeichern.
  4. Der Client-Code (unsere UI-Controller) sollte so wenig Wissen wie möglich über Netzwerkmaterialien haben und das Modell mit der Core Data API abfragen / aktualisieren.

Ich habe die WWDC10-Sitzung 117 zum Erstellen einer servergesteuerten Benutzererfahrung ausgecheckt und einige Zeit damit verbracht, die Frameworks Objective Resource , Core Resource und RestfulCoreData zu überprüfen .

Das Objective Resource Framework kommuniziert nicht alleine mit Core Data und ist lediglich eine REST-Client-Implementierung. Die Core Resource und RestfulCoreData setzen alle voraus, dass Sie in Ihrem Code mit Core Data sprechen, und lösen alle Schrauben und Muttern im Hintergrund auf der Modellebene.

Bis jetzt sieht alles in Ordnung aus und anfangs denke ich, dass entweder Core Resource oder RestfulCoreData alle oben genannten Anforderungen abdecken, aber ... Es gibt ein paar Dinge, die scheinbar nicht richtig gelöst werden:

  1. Der Hauptthread sollte nicht blockiert werden, während lokale Updates auf dem Server gespeichert werden.
  2. Wenn der Speichervorgang fehlschlägt, sollte der Fehler an die Benutzeroberfläche weitergegeben werden und es sollten keine Änderungen im lokalen Kerndatenspeicher gespeichert werden.

Core Resource gibt zufällig alle seine Anforderungen an den Server aus, wenn Sie - (BOOL)save:(NSError **)errorIhren Managed Object Context aufrufen, und kann daher eine korrekte NSError-Instanz der zugrunde liegenden Anforderungen an den Server bereitstellen, die irgendwie fehlschlagen. Der aufrufende Thread wird jedoch blockiert, bis der Speichervorgang abgeschlossen ist. SCHEITERN.

RestfulCoreData hält Ihre -save:Anrufe intakt und führt keine zusätzliche Wartezeit für den Client-Thread ein. Es achtet lediglich auf das NSManagedObjectContextDidSaveNotificationund gibt dann die entsprechenden Anforderungen an den Server im Benachrichtigungshandler aus. Aber auf diese Weise der -save:immer erfolgreich ausgeführt wird (gut, da Core Data ist in Ordnung mit den gespeicherten Änderungen) und dem Client - Code, der tatsächlich nannte es keine Möglichkeit , das zu wissen , hat speichern könnte versagt haben , weil einige auf den Server zu propagieren 404oder 421oder was auch immer Server-seitiger Fehler aufgetreten. Und noch mehr, der lokale Speicher wird die Daten aktualisieren, aber der Server weiß nie über die Änderungen. SCHEITERN.

Daher suche ich nach einer möglichen Lösung / gemeinsamen Praktiken im Umgang mit all diesen Problemen:

  1. Ich möchte nicht, dass der aufrufende Thread bei jedem -save:Anruf blockiert wird , während die Netzwerkanforderungen auftreten.
  2. Ich möchte irgendwie Benachrichtigungen in der Benutzeroberfläche erhalten, dass ein Synchronisierungsvorgang fehlgeschlagen ist.
  3. Ich möchte, dass das Speichern der eigentlichen Kerndaten ebenfalls fehlschlägt, wenn die Serveranforderungen fehlschlagen.

Irgendwelche Ideen?

Eploko
quelle
1
Wow, du hast keine Ahnung, wie viel Ärger du mir durch diese Frage erspart hast. Ich habe derzeit meine App implementiert, damit der Benutzer bei jedem Anruf auf die Daten wartet (allerdings bei einem .NET-Webservice). Ich habe über einen Weg nachgedacht, es asynchron zu machen, konnte aber nicht herausfinden, wie. Vielen Dank für alle Ressourcen, die Sie bereitgestellt haben!
Tejaswi Yerukalapudi
Ausgezeichnete Frage, danke.
Justin
Die Verbindung zu Core Resource ist unterbrochen. Weiß jemand, wo sie jetzt gehostet wird?
Core Resource wird immer noch auf GitHub hier gehostet: github.com/mikelaurence/CoreResource
eploko
Und die ursprüngliche Seite kann auch auf gitHub gefunden werden: github.com/mikelaurence/coreresource.org
eploko

Antworten:

26

Sie sollten sich wirklich RestKit ( http://restkit.org ) für diesen Anwendungsfall ansehen . Es wurde entwickelt, um die Probleme beim Modellieren und Synchronisieren von Remote-JSON-Ressourcen mit einem lokalen Core Data-gestützten Cache zu lösen. Es unterstützt einen Offline-Modus, um vollständig aus dem Cache heraus zu arbeiten, wenn kein Netzwerk verfügbar ist. Die gesamte Synchronisierung erfolgt in einem Hintergrundthread (Netzwerkzugriff, Analyse der Nutzdaten und Zusammenführen verwalteter Objektkontexte). Außerdem gibt es eine Vielzahl von Delegierungsmethoden, mit denen Sie feststellen können, was gerade passiert.

Blake Watters
quelle
18

Es gibt drei grundlegende Komponenten:

  1. Die UI-Aktion und das Fortbestehen der Änderung an CoreData
  2. Behalten Sie diese Änderung bis zum Server bei
  3. Aktualisieren der Benutzeroberfläche mit der Antwort des Servers

Eine NSOperation + NSOperationQueue hilft dabei, die Netzwerkanforderungen ordentlich zu halten. Ein Delegate-Protokoll hilft Ihren UI-Klassen zu verstehen, in welchem ​​Status sich die Netzwerkanforderungen befinden.

@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

Das Protokollformat hängt natürlich von Ihrem spezifischen Anwendungsfall ab, aber im Wesentlichen erstellen Sie einen Mechanismus, mit dem Änderungen auf Ihren Server "übertragen" werden können.

Als nächstes muss die UI-Schleife berücksichtigt werden. Um Ihren Code sauber zu halten, sollten Sie save aufrufen und die Änderungen automatisch auf den Server übertragen. Hierfür können Sie NSManagedObjectContextDidSave-Benachrichtigungen verwenden.

- (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
}

Der Rechenaufwand für das Einfügen der Netzwerkvorgänge sollte relativ gering sein. Wenn jedoch eine merkliche Verzögerung auf der Benutzeroberfläche auftritt, können Sie einfach die Entitäts-IDs aus der Benachrichtigung zum Speichern abrufen und die Vorgänge in einem Hintergrundthread erstellen.

Wenn Ihre REST-API das Batching unterstützt, können Sie sogar das gesamte Array auf einmal senden und dann die Benutzeroberfläche benachrichtigen, dass mehrere Entitäten synchronisiert wurden.

Das einzige Problem, das ich vorhersehe und für das es keine "echte" Lösung gibt, ist, dass der Benutzer nicht warten möchte, bis seine Änderungen auf den Server übertragen werden, damit er weitere Änderungen vornehmen kann. Das einzige gute Paradigma, auf das ich gestoßen bin, ist, dass Sie dem Benutzer erlauben, Objekte weiter zu bearbeiten und ihre Änderungen gegebenenfalls zusammenzufassen, dh Sie drücken nicht jede Speicherbenachrichtigung.

ImHuntingWabbits
quelle
2

Dies wird zu einem Synchronisierungsproblem, das nicht einfach zu lösen ist. Folgendes würde ich tun: Verwenden Sie in Ihrer iPhone-Benutzeroberfläche einen Kontext und laden Sie dann mithilfe eines anderen Kontexts (und eines anderen Threads) die Daten von Ihrem Webdienst herunter. Wenn alles erledigt ist, durchlaufen Sie die unten empfohlenen Synchronisierungs- / Importvorgänge und aktualisieren Sie Ihre Benutzeroberfläche, nachdem alles ordnungsgemäß importiert wurde. Wenn beim Zugriff auf das Netzwerk Probleme auftreten, setzen Sie die Änderungen im Nicht-UI-Kontext einfach zurück. Es ist eine Menge Arbeit, aber ich denke, es ist der beste Weg, sich dem anzunähern.

Kerndaten: Daten effizient importieren

Kerndaten: Change Management

Kerndaten: Multithreading mit Kerndaten

David Weiss
quelle
0

Sie benötigen eine Rückruffunktion, die auf dem anderen Thread ausgeführt wird (demjenigen, auf dem die eigentliche Serverinteraktion stattfindet), und geben dem Ergebniscode / den Fehlerinformationen semiglobale Daten ein, die regelmäßig vom UI-Thread überprüft werden. Stellen Sie sicher, dass die Verkabelung der Nummer, die als Flag dient, atomar ist oder Sie eine Race-Bedingung haben - sagen Sie, wenn Ihre Fehlerantwort 32 Bytes beträgt, benötigen Sie ein int (das atomaren Zugriff haben sollte) und behalten Sie dieses int im Zustand aus / falsch / nicht bereit, bis Ihr größerer Datenblock geschrieben wurde, und erst dann "wahr" schreiben, um den Schalter sozusagen zu betätigen.

Für das korrelierte Speichern auf der Clientseite müssen Sie entweder nur diese Daten behalten und sie erst speichern, wenn Sie vom Server in Ordnung sind. Stellen Sie sicher, dass Sie eine Kinnf-Rollback-Option haben. Angenommen, eine Möglichkeit zum Löschen ist ein Serverfehler.

Beachten Sie, dass es niemals 100% sicher sein wird, es sei denn, Sie führen eine vollständige 2-Phasen-Festschreibungsprozedur durch (das Speichern oder Löschen des Clients kann nach dem Signal vom Server-Server fehlschlagen), aber das kostet Sie mindestens 2 Fahrten zum Server ( kann Sie 4 kosten, wenn Ihre einzige Rollback-Option Löschen ist).

Idealerweise würden Sie die gesamte blockierende Version des Vorgangs in einem separaten Thread ausführen, dafür benötigen Sie jedoch 4.0.

ZXX
quelle