Jak zsynchronizować dane iPhone Core z serwerem internetowym, a następnie przesłać je na inne urządzenia? [Zamknięte]

293

Pracowałem nad metodą synchronizacji podstawowych danych przechowywanych w aplikacji iPhone'a między wieloma urządzeniami, takimi jak iPad lub Mac. Nie ma wielu (jeśli w ogóle) ram synchronizacji do użycia z Core Data na iOS. Zastanawiałem się jednak nad następującą koncepcją:

  1. Dokonano zmiany w lokalnym podstawowym magazynie danych, a zmiana została zapisana. (a) Jeśli urządzenie jest w trybie online, próbuje wysłać zestaw zmian na serwer, w tym identyfikator urządzenia, które wysłało zestaw zmian. (b) Jeśli zestaw zmian nie dotrze do serwera lub urządzenie nie jest w trybie online, aplikacja doda zestaw zmian do kolejki do wysłania, gdy przejdzie w tryb online.
  2. Serwer siedzący w chmurze łączy określone zestawy zmian, które otrzymuje, ze swoją główną bazą danych.
  3. Po scaleniu zestawu zmian (lub kolejki zestawów zmian) na serwerze w chmurze serwer wypycha wszystkie te zestawy zmian na inne urządzenia zarejestrowane na serwerze za pomocą pewnego rodzaju systemu odpytywania. (Myślałem o skorzystaniu z usług Push Apple, ale najwyraźniej według komentarzy nie jest to realny system).

Czy jest coś wymyślonego, o czym muszę pomyśleć? Patrzyłem na frameworki REST, takie jak ObjectiveResource , Core Resource i RestfulCoreData . Oczywiście wszystkie one współpracują z Ruby on Rails, z którymi nie jestem związany, ale jest to miejsce na początek. Główne wymagania, jakie mam dla mojego rozwiązania, to:

  1. Wszelkie zmiany należy przesyłać w tle bez wstrzymywania głównego wątku.
  2. Powinien wykorzystywać możliwie najmniejszą przepustowość.

Myślałem o wielu wyzwaniach:

  1. Upewnij się, że identyfikatory obiektów dla różnych magazynów danych na różnych urządzeniach są dołączone na serwerze. To znaczy, będę mieć tabelę identyfikatorów obiektów i identyfikatorów urządzeń, które są powiązane poprzez odwołanie do obiektu przechowywanego w bazie danych. Będę miał rekord (DatabaseId [unikalny dla tej tabeli], ObjectId [unikalny dla elementu w całej bazie danych], Datafield1, Datafield2), pole ObjectId będzie odnosić się do innej tabeli, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Następnie, gdy urządzenie wypchnie zestaw zmian, przekaże identyfikator urządzenia i identyfikator obiektu z podstawowego obiektu danych w lokalnym magazynie danych. Następnie mój serwer w chmurze sprawdzi obiekt ID i identyfikator urządzenia w tabeli AllObjects i znajdzie rekord do zmiany w tabeli początkowej.
  2. Wszystkie zmiany powinny być oznaczone datą, aby można je było scalić.
  3. Urządzenie będzie musiało sondować serwer bez zużywania zbyt dużej baterii.
  4. Lokalne urządzenia będą również musiały aktualizować wszystko, co jest w pamięci, jeśli / kiedy zmiany zostaną otrzymane z serwera.

Czy brakuje mi czegoś jeszcze? Na jakie ramy powinienem spojrzeć, aby było to możliwe?

Jason
źródło
5
Nie można polegać na otrzymywanych powiadomieniach push. Użytkownik może je po prostu stuknąć, a po otrzymaniu drugiego powiadomienia system operacyjny wyrzuca pierwsze. Powiadomienia wypychane IMO to i tak zły sposób na otrzymywanie aktualizacji synchronizacji, ponieważ przeszkadzają one użytkownikowi. Aplikacja powinna inicjować synchronizację przy każdym uruchomieniu.
Ole Begemann
OK. Dzięki za informację - poza ciągłym odpytywaniem serwera i sprawdzaniem aktualizacji przy uruchomieniu, czy istnieje sposób, aby urządzenie otrzymywało aktualizacje? Chciałbym, aby działała, jeśli aplikacja jest otwarta na wielu urządzeniach jednocześnie.
Jason
1
(Wiem, że trochę się spóźnia, ale każdy może się z tym spotkać i zastanawia się), aby jednocześnie zsynchronizować wiele urządzeń, możesz utrzymać otwarte połączenie z drugim urządzeniem lub serwerem i wysyłać wiadomości, aby powiedzieć innym urządzeniom ), gdy nastąpi aktualizacja. (np. sposób, w jaki działa IRC / komunikatory)
Dan2552
1
@ Dan2552: to, co opisujesz, jest znane jako [długie odpytywanie] [ en.wikipedia.org/wiki/... i jest świetnym pomysłem, jednak otwarte połączenia zużywają sporo energii i przepustowości na urządzeniu mobilnym.
johndodo
1
Oto dobry samouczek od Raya Wenderlicha na temat synchronizacji danych między aplikacją a usługą internetową: raywenderlich.com/15916/…
JRG-Developer

Odpowiedzi:

144

Proponuję uważnie przeczytać i wdrożyć strategię synchronizacji omówioną przez Dana Grovera na konferencji iPhone 2009, dostępną tutaj jako dokument pdf.

Jest to realne rozwiązanie i nie jest trudne do wdrożenia (Dan zaimplementował to w kilku swoich aplikacjach), pokrywając się z rozwiązaniem opisanym przez Chrisa. Szczegółową, teoretyczną dyskusję na temat synchronizacji można znaleźć w artykule Russa Coxa (MIT) i Williama Josephsona (Princeton):

Synchronizacja plików z parami czasu wektora

co dotyczy równie dobrze podstawowych danych z pewnymi oczywistymi modyfikacjami. Zapewnia to ogólnie o wiele bardziej niezawodną i niezawodną strategię synchronizacji, ale wymaga więcej wysiłku, aby zostać poprawnie wdrożonym.

EDYTOWAĆ:

Wygląda na to, że plik pdf Grovera nie jest już dostępny (uszkodzony link, marzec 2015). UPDATE: link jest dostępny poprzez drogę powrotną Maszynie tutaj

Framework Objective-C o nazwie ZSync i opracowany przez Marcusa Zarrę został przestarzały, biorąc pod uwagę, że iCloud wydaje się wreszcie wspierać prawidłową synchronizację podstawowych danych.

Massimo Cafaro
źródło
Czy ktoś ma zaktualizowany link do filmu ZSync? Czy ZSync jest nadal obsługiwany? Widzę, że ostatnia aktualizacja miała miejsce w 2010 roku.
Jeremie Weldin
Ostatnie zatwierdzenie ZSync na githubie miało miejsce we wrześniu 2010 roku, co prowadzi mnie do wniosku, że Marcus przestał go wspierać.
Łatwo psujący się Dave
1
Algorytm opisany przez Dana Grovera jest całkiem dobry. Nie będzie to jednak działać z wielowątkowym kodem serwera (a zatem: w ogóle się nie skaluje), ponieważ nie ma sposobu, aby upewnić się, że klient nie przegapi aktualizacji, gdy czas jest używany do sprawdzania nowych aktualizacji . Proszę, popraw mnie, jeśli się mylę - zabiłbym, aby zobaczyć działającą implementację tego.
masi
1
@Patt, właśnie wysłałem ci plik pdf, zgodnie z żądaniem. Na zdrowie, Massimo Cafaro.
Massimo Cafaro,
3
Brakujące slajdy PDF do synchronizacji danych między platformami autorstwa Dan Grover są dostępne za pośrednictwem Wayback Machine.
Matthew Kairys,
272

Zrobiłem coś podobnego do tego, co próbujesz zrobić. Pozwól, że powiem ci, czego się nauczyłem i jak to zrobiłem.

Zakładam, że istnieje relacja jeden-do-jednego między obiektem Core Data a modelem (lub schematem db) na serwerze. Chcesz po prostu zsynchronizować zawartość serwera z klientami, ale klienci mogą również modyfikować i dodawać dane. Jeśli dobrze to zrozumiałem, czytaj dalej.

Dodałem cztery pola, aby pomóc w synchronizacji:

  1. sync_status - Dodaj to pole tylko do podstawowego modelu danych. Jest używany przez aplikację do ustalenia, czy masz oczekującą zmianę w elemencie. Używam następujących kodów: 0 oznacza brak zmian, 1 oznacza, że ​​jest w kolejce do synchronizacji z serwerem, a 2 oznacza, że ​​jest to obiekt tymczasowy i można go usunąć.
  2. is_deleted - Dodaj to do serwera i podstawowego modelu danych. Usuń zdarzenie nie powinno faktycznie usuwać wiersza z bazy danych lub modelu klienta, ponieważ nie pozostawia nic do synchronizacji z powrotem. Mając tę ​​prostą flagę logiczną, możesz ustawić is_deleted na 1, zsynchronizować ją, a wszyscy będą zadowoleni. Musisz również zmodyfikować kod na serwerze i kliencie, aby wyszukiwać nieusunięte elementy za pomocą „is_deleted = 0”.
  3. last_modified - Dodaj to do serwera i podstawowego modelu danych. To pole powinno być automatycznie aktualizowane przez bieżącą datę i godzinę przez serwer, ilekroć coś zmieni się w tym rekordzie. Klient nigdy nie powinien go modyfikować.
  4. guid - Dodaj globalnie unikalny identyfikator (patrz http://en.wikipedia.org/wiki/Globally_unique_identifier ) do serwera i podstawowego modelu danych. To pole staje się kluczem podstawowym i staje się ważne podczas tworzenia nowych rekordów na kliencie. Zwykle kluczem podstawowym jest rosnąca liczba całkowita na serwerze, ale musimy pamiętać, że treść można utworzyć offline i zsynchronizować później. Identyfikator GUID pozwala nam utworzyć klucz w trybie offline.

Na kliencie dodaj kod, aby ustawić sync_status na 1 w obiekcie modelu, ilekroć coś się zmieni i trzeba zsynchronizować z serwerem. Nowe obiekty modelu muszą generować identyfikator GUID.

Synchronizacja jest pojedynczym żądaniem. Wniosek zawiera:

  • MAKSYMALNY znacznik czasu ostatniej modyfikacji obiektów modelu. Mówi to serwerowi, że zmiany wymagają tylko tego znacznika czasu.
  • Tablica JSON zawierająca wszystkie elementy z sync_status = 1.

Serwer otrzymuje żądanie i wykonuje następujące czynności:

  • Pobiera zawartość z tablicy JSON i modyfikuje lub dodaje zawarte w niej rekordy. Pole Last_modified jest automatycznie aktualizowane.
  • Serwer zwraca tablicę JSON zawierającą wszystkie obiekty ze znacznikiem czasu last_modyfikowanym większym niż znacznik czasu wysłany w żądaniu. Obejmie to właśnie otrzymane obiekty, co stanowi potwierdzenie, że rekord został pomyślnie zsynchronizowany z serwerem.

Aplikacja odbiera odpowiedź i wykonuje następujące czynności:

  • Pobiera zawartość z tablicy JSON i modyfikuje lub dodaje zawarte w niej rekordy. Każdy rekord otrzymuje stan synchronizacji 0.

Mam nadzieję że to pomogło. Użyłem zapisu słowa i modelu zamiennie, ale myślę, że masz pomysł. Powodzenia.

Chris
źródło
2
Pole last_modified istnieje również w lokalnej bazie danych, ale nie jest aktualizowane przez zegar iPhone'a. Jest ustawiany przez serwer i synchronizowany z powrotem. MAKSYMALNA (ostatnia modyfikacja) data jest wysyłana przez aplikację do serwera, aby poinformować go, aby odesłał wszystko zmodyfikowane po tym terminie.
Chris
3
Globalna wartość dla klienta mogłaby zastąpić MAX(last_modified), ale byłoby to zbędne, ponieważ MAX(last_modified)wystarczy. sync_statusMa inną rolę. Jak napisałem wcześniej, MAX(last_modified)określa, co należy zsynchronizować z serwerem, a sync_statusokreśla, co należy zsynchronizować z serwerem.
Chris
2
@Flex_Addicted Thanks. Tak, musisz zreplikować pola dla każdego elementu, który chcesz zsynchronizować. Należy jednak zachować większą ostrożność podczas synchronizowania modelu z relacją (np. 1 do wielu).
Chris
2
@BenPackard - masz rację. Podejście to nie rozwiązuje żadnego konfliktu, więc wygra ostatni klient. Nie musiałem sobie z tym radzić w moich aplikacjach, ponieważ rekordy są edytowane przez jednego użytkownika. Byłbym ciekawy, jak to rozwiązać.
Chris
2
Cześć @noilly, rozważ następujący przypadek: Dokonujesz zmian w obiekcie lokalnym i musisz zsynchronizować go z powrotem z serwerem. Synchronizacja może nastąpić dopiero kilka godzin lub dni później (powiedz, jeśli byłeś offline przez jakiś czas), i w tym czasie aplikacja mogła zostać wyłączona i uruchomiona ponownie kilka razy. W tym przypadku metody na NSManagedObjectContext niewiele by pomogły.
Chris
11

Jeśli nadal szukasz drogi, zajrzyj do telefonu komórkowego Couchbase. To właściwie wszystko, czego chcesz. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

radiospiel
źródło
3
Robi to, co chcesz, jeśli możesz wyrazić swoje dane jako dokumenty, a nie dane relacyjne. Są obejścia, ale nie zawsze są ładne i tego warte.
Jeremie Weldin
dokumenty wystarczają na małe aplikacje
Hai Feng Kao
@radiospiel Twój link jest zepsuty
Mick
Spowoduje to również dodanie zależności, że backend musi być napisany w bazie danych Couchbase. Nawet zacząłem od pomysłu NOSQL do synchronizacji, ale nie mogę ograniczyć mojego backendu do NOSQL, ponieważ MS SQL działa w backend.
thesummersign
@ Mick: wydaje się, że znów działa (lub ktoś naprawił link? Dziękuję)
radiospiel
7

Podobnie jak @Cris zaimplementowałem klasę do synchronizacji między klientem a serwerem i rozwiązałem dotychczas wszystkie znane problemy (wysyłanie / odbieranie danych do / z serwera, scalanie konfliktów na podstawie znaczników czasu, usuwanie duplikatów wpisów w niepewnych warunkach sieciowych, synchronizowanie zagnieżdżonych danych i pliki itp.)

Po prostu powiedz klasie, która jednostka i które kolumny powinna zsynchronizować oraz gdzie jest twój serwer.

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

Możesz znaleźć źródło, działający przykład i więcej instrukcji tutaj: github.com/knagode/M3Synchronization .

knagode
źródło
Czy będzie dobrze, jeśli zmienimy czas urządzenia na nienormalną wartość?
Golden
5

Powiadom użytkownika o konieczności aktualizacji danych za pomocą powiadomienia push. Użyj wątku w tle w aplikacji, aby sprawdzić dane lokalne i dane na serwerze w chmurze, podczas gdy zmiana dzieje się na serwerze, zmień dane lokalne i odwrotnie.

Myślę więc, że najtrudniejszą częścią jest oszacowanie danych, w których strona jest nieważna.

Mam nadzieję, że to może ci pomóc

Stan
źródło
5

Właśnie opublikowałem pierwszą wersję mojego nowego API Core Data Cloud Syncing, znanego jako SynCloud. SynCloud ma wiele różnic w stosunku do iCloud, ponieważ pozwala na interfejs synchronizacji dla wielu użytkowników. Różni się także od innych interfejsów API, ponieważ pozwala na tworzenie relacyjnych danych z wieloma tabelami.

Dowiedz się więcej na http://www.syncloudapi.com

Kompilacja z iOS 6 SDK, jest bardzo aktualna od 27.09.2012.

logan
źródło
5
Witamy w Stack Overflow! Dziękujemy za opublikowanie odpowiedzi! Przeczytaj uważnie FAQ dotyczące autopromocji .
Andrew Barber,
5

Myślę, że dobrym rozwiązaniem problemu z identyfikatorem GUID jest „rozproszony system identyfikacji”. Nie jestem pewien, jaki jest prawidłowy termin, ale myślę, że tak nazywali go dokumenty MS SQL Server (SQL używa / używał tej metody w rozproszonych / synchronizowanych bazach danych). To całkiem proste:

Serwer przypisuje wszystkie identyfikatory. Za każdym razem, gdy synchronizacja jest wykonywana, pierwszą rzeczą, która jest sprawdzana, jest „Ile identyfikatorów pozostałem na tym kliencie?” Jeśli kończy się klient, prosi serwer o nowy blok identyfikatorów. Następnie klient używa identyfikatorów w tym zakresie do nowych rekordów. Działa to doskonale w przypadku większości potrzeb, jeśli można przypisać blok wystarczająco duży, aby „nigdy” nie kończył się przed następną synchronizacją, ale nie tak duży, aby z czasem skończył się serwer. Jeśli klient kiedykolwiek się skończy, obsługa może być dość prosta, po prostu powiedz użytkownikowi „przepraszam, że nie możesz dodać więcej elementów, dopóki nie zsynchronizujesz” ... jeśli dodają tyle elementów, czy nie powinny synchronizować, aby uniknąć nieaktualnych danych w każdym razie problemy?

Myślę, że jest to lepsze niż używanie losowych identyfikatorów GUID, ponieważ losowe identyfikatory GUID nie są w 100% bezpieczne i zwykle muszą być znacznie dłuższe niż standardowy identyfikator (128-bitowy vs 32-bitowy). Zazwyczaj masz indeksy według ID i często przechowujesz numery ID w pamięci, dlatego ważne jest, aby były małe.

Naprawdę nie chciałem publikować jako odpowiedzi, ale nie wiem, czy ktokolwiek widziałby to jako komentarz i myślę, że jest to ważne dla tego tematu i nieujęte w innych odpowiedziach.

eselk
źródło
2

Najpierw zastanów się, ile danych, tabel i relacji będziesz mieć. W moim rozwiązaniu zaimplementowałem synchronizację poprzez pliki Dropbox. Obserwuję zmiany w głównym MOC i zapisuję te dane w plikach (każdy wiersz zapisywany jest jako gzipped json). Jeśli działa połączenie internetowe, sprawdzam, czy są jakieś zmiany w Dropbox (Dropbox daje mi zmiany delta), pobieram je i łączę (ostatnie zwycięstwa), a na koniec umieszczam zmienione pliki. Przed synchronizacją umieszczam plik blokady na Dropbox, aby uniemożliwić innym klientom synchronizację niekompletnych danych. Podczas pobierania zmian można bezpiecznie pobierać tylko częściowe dane (np. Utracone połączenie internetowe). Po zakończeniu pobierania (całkowicie lub częściowo) zaczyna się ładowanie plików do podstawowych danych. Gdy istnieją nierozwiązane relacje (nie wszystkie pliki są pobierane), zatrzymuje ładowanie plików i próbuje zakończyć pobieranie później. Relacje są przechowywane tylko jako GUID, dzięki czemu mogę łatwo sprawdzić, które pliki należy załadować, aby mieć pełną integralność danych. Synchronizacja rozpoczyna się po wprowadzeniu zmian w podstawowych danych. Jeśli nie ma żadnych zmian, sprawdza zmiany w Dropbox co kilka minut i podczas uruchamiania aplikacji. Dodatkowo, gdy zmiany są wysyłane na serwer, wysyłam transmisję do innych urządzeń, aby poinformować ich o zmianach, aby mogli szybciej synchronizować. Każda zsynchronizowana jednostka ma właściwość GUID (identyfikator GUID służy również jako nazwa pliku dla plików wymiany). Mam również bazę danych Sync, w której przechowuję wersję Dropbox każdego pliku (mogę to porównać, gdy delta Dropbox resetuje jego stan). Pliki zawierają również nazwę encji, stan (usunięty / nie usunięty), identyfikator GUID (taki sam jak nazwa pliku), rewizję bazy danych (w celu wykrycia migracji danych lub uniknięcia synchronizacji z nigdy wersjami aplikacji) i oczywiście dane (jeśli wiersz nie zostanie usunięty). dzięki czemu mogę łatwo sprawdzić, które pliki należy załadować, aby uzyskać pełną integralność danych. Synchronizacja rozpoczyna się po wprowadzeniu zmian w podstawowych danych. Jeśli nie ma żadnych zmian, sprawdza zmiany w Dropbox co kilka minut i podczas uruchamiania aplikacji. Dodatkowo, gdy zmiany są wysyłane na serwer, wysyłam transmisję do innych urządzeń, aby poinformować ich o zmianach, aby mogli szybciej synchronizować. Każda zsynchronizowana jednostka ma właściwość GUID (identyfikator GUID służy również jako nazwa pliku dla plików wymiany). Mam również bazę danych Sync, w której przechowuję wersję Dropbox każdego pliku (mogę to porównać, gdy delta Dropbox resetuje jego stan). Pliki zawierają również nazwę encji, stan (usunięty / nie usunięty), identyfikator GUID (taki sam jak nazwa pliku), rewizję bazy danych (w celu wykrycia migracji danych lub uniknięcia synchronizacji z nigdy wersjami aplikacji) i oczywiście dane (jeśli wiersz nie zostanie usunięty). dzięki czemu mogę łatwo sprawdzić, które pliki należy załadować, aby uzyskać pełną integralność danych. Synchronizacja rozpoczyna się po wprowadzeniu zmian w podstawowych danych. Jeśli nie ma żadnych zmian, sprawdza zmiany w Dropbox co kilka minut i podczas uruchamiania aplikacji. Dodatkowo, gdy zmiany są wysyłane na serwer, wysyłam transmisję do innych urządzeń, aby poinformować ich o zmianach, aby mogli szybciej synchronizować. Każda zsynchronizowana jednostka ma właściwość GUID (identyfikator GUID służy również jako nazwa pliku dla plików wymiany). Mam również bazę danych Sync, w której przechowuję wersję Dropbox każdego pliku (mogę to porównać, gdy delta Dropbox resetuje jego stan). Pliki zawierają również nazwę encji, stan (usunięty / nie usunięty), identyfikator GUID (taki sam jak nazwa pliku), rewizję bazy danych (w celu wykrycia migracji danych lub uniknięcia synchronizacji z nigdy wersjami aplikacji) i oczywiście dane (jeśli wiersz nie zostanie usunięty).

To rozwiązanie działa dla tysięcy plików i około 30 podmiotów. Zamiast Dropbox mogłem użyć magazynu kluczy / wartości jako usługi REST, którą chcę zrobić później, ale nie mam na to czasu :) Na razie moim zdaniem moje rozwiązanie jest bardziej niezawodne niż iCloud i, co bardzo ważne, Mam pełną kontrolę nad tym, jak to działa (głównie dlatego, że to mój własny kod).

Innym rozwiązaniem jest zapisywanie zmian MOC jako transakcji - o wiele mniej plików będzie wymienianych z serwerem, ale trudniej jest wykonać początkowe ładowanie w odpowiedniej kolejności do pustych danych podstawowych. iCloud działa w ten sposób, a także inne rozwiązania do synchronizacji mają podobne podejście, np . TICoreDataSync .

-- AKTUALIZACJA

Po pewnym czasie przeprowadziłem migrację do zespołów - polecam to rozwiązanie zamiast wynaleźć koło.

thom_ek
źródło