Jak zsynchronizować CoreData i usługę REST web service asynchronicznie i w tym samym czasie poprawnie propagować wszelkie błędy REST do interfejsu użytkownika

Hej, pracuję nad warstwą modelu dla naszej aplikacji tutaj.

Niektóre wymagania są takie:

  1. powinno działać na iPhone OS 3.0+.
  2. źródłem naszych danych jest aplikacja RESTful Rails.
  3. powinniśmy buforować dane lokalnie, używając podstawowych danych.
  4. kod klienta (nasze kontrolery interfejsu użytkownika) powinien mieć jak najmniej wiedzy na temat wszelkich problemów sieciowych, jak to możliwe i powinien odpytywać / aktualizować model za pomocą Core Data API.

I ' ve sprawdziliśmy sesję WWDC10 117 na budowaniu doświadczenia użytkownika opartego na serwerze, spędziliśmy trochę czasu na sprawdzaniu obiektywnego zasobu , Core Resource , and RestfulCoreData Framework.

Objective Resource framework nie rozmawia z Core Data samodzielnie i jest jedynie implementacją klienta REST. Core Resource i RestfulCoreData zakładają, że rozmawiasz z podstawowymi danymi w kodzie i rozwiązują wszystkie nakrętki i śruby w tle na warstwa modelu.

Na razie wszystko wygląda dobrze i początkowo myślałem, że albo Core Resource albo RestfulCoreData pokryje wszystkie powyższe wymagania, ale... Jest kilka rzeczy, których żadna z nich nie rozwiązuje poprawnie:

  1. główny wątek nie powinien być blokowany podczas zapisywania lokalnych aktualizacji na serwerze.
  2. jeśli operacja zapisu nie powiedzie się, błąd powinien być propagowany do interfejsu użytkownika i żadne zmiany nie powinny być zapisywane w lokalnych danych podstawowych magazyn.

Core Resource wydaje wszystkie swoje żądania do serwera, gdy wywołujesz - (BOOL)save:(NSError **)error W kontekście zarządzanego obiektu i dlatego jest w stanie dostarczyć poprawną instancję nserror bazowych żądań do serwera w jakiś sposób. Ale blokuje wątek wywołujący, dopóki operacja save nie zakończy się. Porażka.

RestfulCoreData utrzymuje połączenia -save: w stanie nienaruszonym i nie wprowadza dodatkowego czasu oczekiwania na wątek klienta. Tylko pilnuje NSManagedObjectContextDidSaveNotification następnie wysyła odpowiednie żądania do serwera w obsłudze powiadomień. Ale w ten sposób wywołanie -save: zawsze kończy się pomyślnie (cóż, Dane podstawowe są w porządku z zapisanymi zmianami) i Kod klienta, który je wywołał, nie ma możliwości, aby wiedzieć, że zapis mógł nie zostać wysłany na serwer z powodu jakiegoś 404 lub 421 lub jakiegokolwiek błędu po stronie serwera. A co więcej, lokalna pamięć staje się mieć dane aktualizowane, ale serwer nigdy nie wie o zmiany. Porażka.

Więc szukam możliwego rozwiązania / wspólnych praktyk w radzeniu sobie z tymi wszystkimi problemami:

  1. nie chcę, aby wątek wywołujący blokował każde wywołanie -save: podczas gdy występują żądania sieciowe.
  2. chcę w jakiś sposób otrzymywać powiadomienia w interfejsie użytkownika, że jakaś operacja synchronizacji poszła źle.
  3. chcę, aby rzeczywiste dane Core Save również nie powiodły się, jeśli żądania serwera nie powiodą się.
Jakieś pomysły?
Author: eploko, 2010-06-20

4 answers

Naprawdę powinieneś rzucić okiem na RestKit ( http://restkit.org ) w tym przypadku użycia. Został zaprojektowany w celu rozwiązania problemów modelowania i synchronizacji zdalnych zasobów JSON do lokalnego core Data backed cache. Obsługuje tryb offline do pracy całkowicie z pamięci podręcznej, gdy nie ma dostępnej sieci. Cała synchronizacja odbywa się w wątku tła (dostęp do sieci, parsowanie ładunku i scalanie kontekstu zarządzanych obiektów). dalej.

 26
Author: Blake Watters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-12 03:48:41

Istnieją trzy podstawowe składniki:

  1. akcja UI i utrzymanie zmiany na CoreData
  2. utrzymanie tej zmiany na serwerze
  3. Odświeżanie interfejsu użytkownika z odpowiedzią serwera

Nsoperation + NSOperationQueue pomoże uporządkować żądania sieci. Protokół delegata pomoże klasom interfejsu zrozumieć, w jakim stanie są żądania sieciowe, coś w stylu:

@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

Format protokołu będzie oczywiście zależał od Twojego konkretny przypadek użycia, ale zasadniczo to, co tworzysz, to mechanizm, za pomocą którego zmiany mogą być "popychane" na twój serwer.

Następnie należy wziąć pod uwagę pętlę interfejsu użytkownika, aby zachować czystość kodu, dobrze byłoby wywołać save: i automatycznie przenieść zmiany na serwer. W tym celu możesz użyć powiadomień NSManagedObjectContextDidSave.

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

Obciążenia obliczeniowe do wprowadzania operacji sieciowych powinny być raczej niskie, jednak jeśli tworzy to zauważalne opóźnienie na interfejs użytkownika można po prostu pobrać identyfikatory elementów z powiadomienia Zapisz i utworzyć operacje w wątku w tle.

Jeśli Twoje API REST obsługuje grupowanie, możesz nawet wysłać całą tablicę naraz, a następnie powiadomić interfejs użytkownika, że wiele jednostek zostało zsynchronizowanych.

Jedynym problemem, który przewiduję, a dla którego nie ma "prawdziwego" rozwiązania jest to, że użytkownik nie będzie chciał czekać, aż jego zmiany zostaną wypchnięte na serwer, aby móc wprowadzić więcej zmian. Jedyny dobrym paradygmatem, na który się natknąłem jest to, że pozwalasz użytkownikowi na zachowanie edycji obiektów i wsadowanie ich edycji razem, gdy jest to właściwe, tzn. nie naciskasz na każde powiadomienie o zapisie.

 18
Author: ImHuntingWabbits,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-17 00:09:30

Staje się to problemem synchronizacji, a nie łatwym do rozwiązania. Oto, co bym zrobił: w interfejsie użytkownika iPhone ' a użyj jednego kontekstu, a następnie za pomocą innego kontekstu (i innego wątku) Pobierz dane z usługi internetowej. Po tym wszystkim przejdź przez procesy synchronizacji / importowania zalecane poniżej, a następnie Odśwież swój interfejs po tym, jak wszystko zostało poprawnie zaimportowane. Jeśli coś pójdzie źle podczas uzyskiwania dostępu do sieci, po prostu Cofnij zmiany w kontekście spoza interfejsu użytkownika. To sporo pracy, ale myślę, że to najlepsze jak można się do tego zbliżyć.

Dane Podstawowe: Efektywny Import Danych

Podstawowe Dane: Zarządzanie Zmianami

Dane rdzeniowe: wielowątkowe z danymi rdzeniowymi

 2
Author: David Weiss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-06 22:02:48

Potrzebujesz funkcji wywołania zwrotnego, która będzie działać w drugim wątku (tym, w którym dzieje się rzeczywista interakcja z serwerem), a następnie umieścić kod wyniku / informacje o błędzie pół-globalne dane, które będą okresowo sprawdzane przez wątek interfejsu. Upewnij się, że wirting liczby, która służy jako flaga jest atomowa lub masz zamiar mieć warunek wyścigu-powiedzmy, jeśli odpowiedź na błąd jest 32 bajtów potrzebujesz int (whihc powinien mieć atomic acces), a następnie zachować, że int w off / false / not-ready stan, aż twój większy blok danych zostanie napisany i dopiero wtedy napisz "true", aby odwrócić przełącznik, że tak powiem.

Dla skorelowanego zapisywania po stronie klienta musisz albo zachować te dane, a nie zapisać je, dopóki nie otrzymasz OK z serwera upewnij się, że masz kinnf opcji rollback-powiedzmy, że sposób na usunięcie jest server failed.

Uważaj, że nigdy nie będzie w 100% bezpieczny, chyba że wykonasz PEŁNĄ 2-fazową procedurę commit (Client save lub delete może zawieść po sygnale z serwera), ale to będzie kosztować 2 wycieczki na serwer co najmniej (może kosztować 4, Jeśli jedyną opcją rollback jest Usuń).

Najlepiej zrobić całą blokującą wersję operacji na osobnym wątku, ale do tego potrzebujesz 4.0.

 0
Author: ZXX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-29 13:32:34