Aplikacje Firebase działają nawet wtedy, gdy aplikacja tymczasowo utraci połączenie z siecią. Firebase udostępnia też narzędzia do zapisywania danych lokalnie, zarządzania obecnością i obsługi opóźnień.
Trwałość na dysku
Aplikacje Firebase automatycznie radzą sobie z tymczasowymi przerwami w działaniu sieci. Dane w pamięci podręcznej są dostępne w trybie offline, a Firebase ponownie wysyła wszystkie zapisy, gdy połączenie z siecią zostanie przywrócone.
Gdy włączysz trwałość na dysku, aplikacja zapisze dane lokalnie na urządzeniu, aby mogła zachować stan w trybie offline, nawet jeśli użytkownik lub system operacyjny ponownie uruchomi aplikację.
Możesz włączyć trwałość dysku za pomocą jednej linii kodu.
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
Zachowanie trwałości
Po włączeniu trwałości wszystkie dane, które klient Firebase Realtime Databasezsynchronizowałby w trybie online, są przechowywane na dysku i są dostępne w trybie offline, nawet gdy użytkownik lub system operacyjny ponownie uruchomi aplikację. Oznacza to, że aplikacja działa tak samo jak w trybie online, korzystając z danych lokalnych przechowywanych w pamięci podręcznej. W przypadku lokalnych aktualizacji nadal będą wywoływane procedury obsługi wywołań z zewnętrznych źródeł.
Klient Firebase Realtime Database automatycznie przechowuje kolejkę wszystkich operacji zapisu wykonywanych, gdy aplikacja jest offline. Gdy trwałość jest włączona, kolejka jest również zapisywana na dysku, dzięki czemu wszystkie operacje zapisu są dostępne, gdy użytkownik lub system operacyjny ponownie uruchomi aplikację. Gdy aplikacja odzyska łączność, wszystkie operacje zostaną wysłane na serwer Firebase Realtime Database.
Jeśli Twoja aplikacja korzysta z usługi Uwierzytelnianie Firebase, klient Firebase Realtime Database przechowuje token uwierzytelniający użytkownika po ponownym uruchomieniu aplikacji. Jeśli token uwierzytelniania wygaśnie, gdy aplikacja jest offline, klient wstrzyma operacje zapisu do czasu ponownego uwierzytelnienia użytkownika. W przeciwnym razie operacje zapisu mogą się nie udać z powodu reguł zabezpieczeń.
Utrzymywanie aktualności danych
Firebase Realtime Database synchronizuje i przechowuje lokalną kopię danych dla aktywnych słuchaczy. Możesz też synchronizować określone lokalizacje.
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
Klient Firebase Realtime Database automatycznie pobiera dane z tych lokalizacji i utrzymuje je w synchronizacji, nawet jeśli referencja nie ma aktywnych słuchaczy. Synchronizację możesz ponownie wyłączyć za pomocą tego wiersza kodu.
Swift
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
Domyślnie w pamięci podręcznej jest przechowywanych 10 MB wcześniej zsynchronizowanych danych. Powinna to być wystarczająca wartość w większości przypadków. Jeśli rozmiar pamięci podręcznej przekroczy skonfigurowany rozmiar, Firebase Realtime Database usunie dane, które były używane najrzadziej. Dane, które są synchronizowane, nie są usuwane z pamięci podręcznej.
Wysyłanie zapytań do danych offline
Firebase Realtime Database przechowuje dane zwrócone przez zapytanie do użytku w trybie offline. W przypadku zapytań utworzonych w trybie offline funkcja Firebase Realtime Database nadal działa w przypadku wcześniej załadowanych danych. Jeśli żądane dane nie zostały załadowane, Firebase Realtime Database wczyta dane z lokalnej pamięci podręcznej. Gdy połączenie z siecią zostanie przywrócone, dane zostaną załadowane i odzwierciedlą zapytanie.
Ten kod na przykład wypisuje Firebase Realtime Database najnowszych wyników
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [[[scoresRef queryOrderedByValue] queryLimitedToLast:4] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) { NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value); }];
Załóżmy, że użytkownik traci połączenie, przechodzi w tryb offline i ponownie uruchamia aplikację. W trybie offline aplikacja wysyła zapytanie o 2 ostatnie elementy z tego samego miejsca. To zapytanie zwróci 2 ostatnie pozycje, ponieważ aplikacja wczytała wszystkie 4 pozycje z zapytania powyżej.
Swift
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
Objective-C
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) { NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value); }];
W powyższym przykładzie klient Firebase Realtime Database wywołuje zdarzenia „child added” dla 2 dinozaurów z najwyższą liczbą punktów, korzystając z trwałej pamięci podręcznej. Nie wywoła ono jednak zdarzenia „value”, ponieważ aplikacja nigdy nie wykonała tego zapytania w trybie online.
Jeśli aplikacja poprosi o ostatnie 6 elementów w trybie offline, od razu otrzyma zdarzenia „child added” dotyczące 4 elementów z pamięci podręcznej. Gdy urządzenie znów połączy się z internetem, klient Firebase Realtime Database zsynchronizuje się z serwerem i otrzyma w aplikacji 2 ostateczne zdarzenia „child added” i „value”.
Obsługa transakcji offline
Wszystkie transakcje przeprowadzane, gdy aplikacja jest offline, są umieszczane w kolejce. Gdy aplikacja ponownie nawiąże połączenie z internetem, transakcje zostaną wysłane do serwera Realtime Database.
Zarządzanie obecnością
W przypadku aplikacji działających w czasie rzeczywistym często przydaje się wykrywanie momentów, w których klienci łączą się z serwerem i z niego rozłączają. Możesz na przykład oznaczyć użytkownika jako „offline”, gdy jego klient się rozłączy.
Klienci Bazy danych Firebase udostępniają proste prymitywy, których można używać do zapisywania danych w bazie danych, gdy klient rozłączy się z serwerami Bazy danych Firebase. Te aktualizacje mają miejsce niezależnie od tego, czy klient odłączy się prawidłowo, czy nie. Dzięki temu możesz polegać na tym, że dane zostaną oczyszczone nawet wtedy, gdy połączenie zostanie utracone lub klient ulegnie awarii. Po odłączeniu można wykonywać wszystkie operacje zapisu, w tym ustawianie, aktualizowanie i usuwanie.
Oto prosty przykład zapisywania danych po rozłączeniu za pomocą prymitywu onDisconnect
:
Swift
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
Objective-C
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"]; // Write a string when this client loses connection [presenceRef onDisconnectSetValue:@"I disconnected!"];
Jak działa onDisconnect
Gdy utworzysz operację onDisconnect()
, będzie ona działać na serwerze Firebase Realtime Database. Serwer sprawdza zabezpieczenia, aby upewnić się, że użytkownik może wykonać żądane zdarzenie zapisu, i informuje aplikację, jeśli jest ono nieprawidłowe. Następnie serwer monitoruje połączenie. Jeśli w jakimś momencie połączenie zostanie przerwane lub klient Realtime Database zamknie je aktywnie, serwer sprawdza zabezpieczenia po raz drugi (aby upewnić się, że operacja jest nadal ważna), a następnie wywołuje zdarzenie.
Aplikacja może użyć wywołania zwrotnego operacji zapisu, aby upewnić się, że onDisconnect
został prawidłowo dołączony:
Swift
presenceRef.onDisconnectRemoveValue { error, reference in if let error = error { print("Could not establish onDisconnect event: \(error)") } }
Objective-C
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) { if (error != nil) { NSLog(@"Could not establish onDisconnect event: %@", error); } }];
Wydarzenie onDisconnect
można też anulować, wywołując funkcję .cancel()
:
Swift
presenceRef.onDisconnectSetValue("I disconnected") // some time later when we change our minds presenceRef.cancelDisconnectOperations()
Objective-C
[presenceRef onDisconnectSetValue:@"I disconnected"]; // some time later when we change our minds [presenceRef cancelDisconnectOperations];
Wykrywanie stanu połączenia
W przypadku wielu funkcji związanych z obecnością przydatne jest, aby aplikacja wiedziała, kiedy jest online, a kiedy offline. Firebase Realtime Database
udostępnia specjalną lokalizację w adresie /.info/connected
, która jest aktualizowana za każdym razem, gdy zmienia się stan połączenia klienta Firebase Realtime Database. Oto przykład:
Swift
let connectedRef = Database.database().reference(withPath: ".info/connected") connectedRef.observe(.value, with: { snapshot in if snapshot.value as? Bool ?? false { print("Connected") } else { print("Not connected") } })
Objective-C
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"]; [connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { if([snapshot.value boolValue]) { NSLog(@"connected"); } else { NSLog(@"not connected"); } }];
/.info/connected
to wartość logiczna, która nie jest synchronizowana między klientami Realtime Database, ponieważ zależy od stanu klienta. Innymi słowy, jeśli jeden klient odczytuje wartość /.info/connected
jako fałsz, nie ma gwarancji, że inny klient również odczyta wartość jako fałsz.
Czas obsługi
Sygnatury czasowe serwera
Serwery Firebase Realtime Database udostępniają mechanizm wstawiania sygnatur czasowych wygenerowanych na serwerze jako danych. Ta funkcja w połączeniu z onDisconnect
umożliwia łatwe i niezawodne zapisywanie informacji o czasie odłączenia klienta Realtime Database:
Swift
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Objective-C
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"]; [userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
Zniekształcenie zegara
Funkcja firebase.database.ServerValue.TIMESTAMP
jest znacznie dokładniejsza i preferowana w przypadku większości operacji odczytu/zapisu, ale czasami może być przydatna do oszacowania odchylenia zegara klienta względem serwerów Firebase Realtime Database. Do lokalizacji /.info/serverTimeOffset
możesz dołączyć funkcję wywołania zwrotnego, aby uzyskać wartość w milisekundach, którą klienci Firebase Realtime Database dodają do lokalnego czasu zgłoszonego (czas epoki w milisekundach), aby oszacować czas serwera. Pamiętaj, że dokładność tego przesunięcia może być zależna od opóźnienia w sieci, dlatego jest przydatna głównie do wykrywania dużych (> 1 sekunda) rozbieżności w czasie zegara.
Swift
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset") offsetRef.observe(.value, with: { snapshot in if let offset = snapshot.value as? TimeInterval { print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)") } })
Objective-C
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"]; [offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue]; NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset; NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs); }];
Przykładowa aplikacja do sprawdzania obecności
Łącząc operacje rozłączania z monitorowaniem stanu połączenia i datą i godzinę serwera, możesz tworzyć systemy obecności użytkowników. W tym systemie każdy użytkownik przechowuje dane w lokalizacji bazy danych, aby wskazać, czy klient Realtime Database jest online. Klienci ustawiają tę lokalizację na „Prawda” (true) po połączeniu z internetem i dodają sygnaturę czasową po rozłączeniu. Ta sygnatura czasowa wskazuje, kiedy dany użytkownik był ostatnio online.
Pamiętaj, że aplikacja powinna umieścić operacje rozłączania w kolejce przed oznaczeniem użytkownika jako online, aby uniknąć warunków wyścigu w przypadku utraty połączenia z siecią przez klienta, zanim oba polecenia zostaną wysłane na serwer.
Oto prosty system wykrywania obecności użytkownika:
Swift
// since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections") // stores the timestamp of my last disconnect (the last time I was seen online) let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") let connectedRef = Database.database().reference(withPath: ".info/connected") connectedRef.observe(.value, with: { snapshot in // only handle connection established (or I've reconnected after a loss of connection) guard snapshot.value as? Bool ?? false else { return } // add this device to my connections list let con = myConnectionsRef.childByAutoId() // when this device disconnects, remove it. con.onDisconnectRemoveValue() // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition // where you set the user's presence to true and the client disconnects before the // onDisconnect() operation takes effect, leaving a ghost user. // this value could contain info about the device or a timestamp instead of just true con.setValue(true) // when I disconnect, update the last time I was seen online lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp()) })
Objective-C
// since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"]; // stores the timestamp of my last disconnect (the last time I was seen online) FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"]; FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"]; [connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { if([snapshot.value boolValue]) { // connection established (or I've reconnected after a loss of connection) // add this device to my connections list FIRDatabaseReference *con = [myConnectionsRef childByAutoId]; // when this device disconnects, remove it [con onDisconnectRemoveValue]; // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition // where you set the user's presence to true and the client disconnects before the // onDisconnect() operation takes effect, leaving a ghost user. // this value could contain info about the device or a timestamp instead of just true [con setValue:@YES]; // when I disconnect, update the last time I was seen online [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]]; } }];