(Opcjonalnie) Prototypowanie i testowanie za pomocą Firebase Local Emulator Suite
Zanim omówimy sposób odczytu i zapisu danych do Realtime Database przez aplikację, zapoznamy Cię z zestawem narzędzi, które możesz wykorzystać do tworzenia prototypów i testowania funkcji Realtime Database: Firebase Local Emulator Suite. Jeśli testujesz inne dane optymalizowanie reguł zabezpieczeń lub wyszukiwanie opłacalny sposób interakcji z backendem, który daje możliwość pracy lokalnej bez wdrażania aktywnych usług może być świetnym pomysłem.
Emulator Realtime Database jest częścią interfejsu Local Emulator Suite, który umożliwia aplikacji interakcję z treścią i konfiguracją emulowanej bazy danych a także opcjonalnie emulowane zasoby projektu (funkcje, inne bazy danych, i reguły zabezpieczeń).
Aby użyć emulatora Realtime Database, wystarczy wykonać kilka czynności:
- Dodajesz wiersz kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
- W katalogu głównym lokalnego katalogu projektu uruchom
firebase emulators:start
. - Wykonywanie wywołań z prototypowego kodu aplikacji za pomocą platformy Realtime Database pakietu SDK w zwykły sposób lub za pomocą interfejsu API REST Realtime Database.
Dostępny jest szczegółowy przewodnik dotyczący elementów Realtime Database i Cloud Functions. Warto też zapoznać się z wprowadzeniem do funkcji Local Emulator Suite.
Pobieranie obiektu FIRDatabaseReference
Aby móc odczytywać lub zapisywać dane z bazy danych, musisz mieć instancję
FIRDatabaseReference
:
Swift
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
Zapisywanie danych
Ten dokument zawiera podstawowe informacje o odczytywaniu i zapisywaniu danych Firebase.
Dane Firebase są zapisywane w odwołaniu Database
i pobierane przez
przez dodanie detektora asynchronicznego do pliku referencyjnego. Detektor zostaje aktywowany
raz dla początkowego stanu danych i ponownie po każdej zmianie danych.
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu możesz użyć funkcji setValue
, aby zapisać dane w określonym
zastępując wszystkie istniejące dane w tej ścieżce. Możesz użyć tej metody, aby:
- Typy kart odpowiadające dostępnym typom JSON:
NSString
NSNumber
NSDictionary
NSArray
Użytkownika z polem setValue
możesz dodać w ten sposób:
Swift
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
Użycie setValue
w ten sposób powoduje zastąpienie danych w wybranej lokalizacji, w tym wszystkich węzłów podrzędnych. Nadal możesz jednak zaktualizować dziecko bez
cały obiekt jest tworzony na nowo. Jeśli chcesz zezwolić użytkownikom na aktualizowanie swoich profili
możesz zmienić nazwę użytkownika w następujący sposób:
Swift
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
Odczytywanie danych
Odczytuj dane przez nasłuchiwanie zdarzeń wartości
Aby odczytać dane na ścieżce i nasłuchiwać zmian, użyj funkcji
observeEventType:withBlock
z FIRDatabaseReference
do obserwacji
Wydarzenia: FIRDataEventTypeValue
.
Typ zdarzenia | Typowe zastosowanie |
---|---|
FIRDataEventTypeValue |
Odczyt i nasłuchiwanie zmian w całej zawartości ścieżki. |
Zdarzenia FIRDataEventTypeValue
możesz używać do odczytu danych na danej ścieżce w stanie, w jakim były one dostępne w momencie zdarzenia. Ta metoda jest wyzwalana jednorazowo, gdy
za każdym razem, gdy dane, w tym dzieci,
zmian. Wywołanie zwrotne zdarzenia jest przekazywane w postaci snapshot
zawierającej wszystkie dane w
lokalizacji, w tym danych podrzędnych. Jeśli nie ma żadnych danych, podsumowanie zwróci wartość false
, gdy wywołasz funkcję exists()
, i wartość nil
, gdy odczytasz jej właściwość value
.
W poniższym przykładzie pokazano, jak aplikacja do blogowania społecznościowego pobiera szczegóły posta z bazy danych:
Swift
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
Detektor odbiera obiekt FIRDataSnapshot
zawierający dane w określonym miejscu
znajduje się w bazie danych w chwili wystąpienia zdarzenia we właściwości value
. Ty
mogą przypisać wartości do odpowiedniego typu natywnego, np. NSDictionary
.
Jeśli w danej lokalizacji nie ma danych, value
ma wartość nil
.
Odczytaj dane raz
Odczyt raz za pomocą getData()
Pakiet SDK służy do zarządzania interakcjami z serwerami baz danych niezależnie od tego, czy jest online lub offline.
Ogólnie do odczytu wartości zdarzeń należy używać opisanych powyżej technik aby otrzymywać powiadomienia o aktualizacjach danych z backendu. Te techniki zmniejszają wykorzystanie zasobów i płatności, są zoptymalizowane, aby zapewniać użytkownikom korzystają z internetu i offline.
Jeśli dane są potrzebne tylko raz, możesz użyć funkcji getData()
, aby uzyskać zrzut
z bazy danych. Jeśli z jakiegoś powodu getData()
nie może zwrócić
wartość serwera, klient sprawdzi pamięć podręczną w lokalnej pamięci masowej i zwróci błąd
, jeśli nadal nie możesz znaleźć wartości.
Ten przykład pokazuje, jak pobrać nazwę użytkownika widoczną publicznie z bazy danych:
Swift
do { let snapshot = try await ref.child("users/\(uid)/username").getData() let userName = snapshot.value as? String ?? "Unknown" } catch { print(error) }
Objective-C
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid]; [[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) { if (error) { NSLog(@"Received an error %@", error); return; } NSString *userName = snapshot.value; }];
Niepotrzebne używanie getData()
może zwiększyć wykorzystanie przepustowości i spowodować spadek wydajności. Można temu zapobiec, używając odbiorcy w czasie rzeczywistym, jak pokazano powyżej.
jednokrotne odczytywanie danych z obserwatorem;
W niektórych przypadkach możesz chcieć, aby zwracana była wartość z lokalnej pamięci podręcznej
natychmiast, bez sprawdzania aktualizacji na serwerze. W
można użyć funkcji observeSingleEventOfType
, aby pobrać dane z
do pamięci podręcznej dysku lokalnego.
Jest to przydatne w przypadku danych, które trzeba wczytać tylko raz i w pewnym stopniu często się zmieniają lub wymagają aktywnego słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do wczytywania profilu użytkownika, gdy ten zaczyna pisać nowy post:
Swift
let userID = Auth.auth().currentUser?.uid ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in // Get user value let value = snapshot.value as? NSDictionary let username = value?["username"] as? String ?? "" let user = User(username: username) // ... }) { error in print(error.localizedDescription) }
Objective-C
NSString *userID = [FIRAuth auth].currentUser.uid; [[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { // Get user value User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]]; // ... } withCancelBlock:^(NSError * _Nonnull error) { NSLog(@"%@", error.localizedDescription); }];
Aktualizowanie lub usuwanie danych
Zaktualizuj określone pola
Jednoczesne zapisywanie do określonych elementów podrzędnych węzła bez zastępowania innych
węzłów podrzędnych, użyj metody updateChildValues
.
Wywołując updateChildValues
, możesz zaktualizować wartości podrzędne niższego poziomu przez
i podaj ścieżkę dostępu do klucza. Jeśli dane są przechowywane w wielu lokalizacjach na potrzeby skalowania
możesz zaktualizować wszystkie wystąpienia tych danych za pomocą funkcji
rozpowszechnianie danych. Na przykład plik
aplikacja do obsługi blogów społecznościowych może utworzyć posta i jednocześnie zaktualizować go
kanał ostatniej aktywności i kanał aktywności użytkownika publikującego. W tym celu
aplikacja do tworzenia blogów używa takiego kodu:
Swift
guard let key = ref.child("posts").childByAutoId().key else { return } let post = ["uid": userID, "author": username, "title": title, "body": body] let childUpdates = ["/posts/\(key)": post, "/user-posts/\(userID)/\(key)/": post] ref.updateChildValues(childUpdates)
Objective-C
NSString *key = [[_ref child:@"posts"] childByAutoId].key; NSDictionary *post = @{@"uid": userID, @"author": username, @"title": title, @"body": body}; NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post, [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post}; [_ref updateChildValues:childUpdates];
W tym przykładzie użyto metody childByAutoId
do utworzenia w węźle posta posta zawierającego posty dla:
wszystkich użytkowników w organizacji /posts/$postid
i jednocześnie pobieraj klucz za pomocą polecenia
getKey()
Za pomocą klucza można utworzyć drugi wpis w
posty na blogu /user-posts/$userid/$postid
.
Korzystając z tych ścieżek, można wprowadzać zmiany w wielu lokalizacjach jednocześnie
drzewo JSON z pojedynczym wywołaniem updateChildValues
, jak w tym przykładzie
utworzy nowy post w obu lokalizacjach. Równoczesne aktualizacje dokonywane w ten sposób
są niepodzielne: wszystkie aktualizacje zakończą się sukcesem, albo wszystkie aktualizacje kończą się niepowodzeniem.
Dodaj blok ukończenia
Jeśli chcesz się dowiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać
możesz użyć tej funkcji. Zarówno setValue
, jak i updateChildValues
przyjmują opcjonalny
blok ukończenia, który jest wywoływany po zatwierdzeniu zapisu w funkcji
w bazie danych. Ten detektor może być przydatny do śledzenia danych, które zostały
zapisane, a które dane są nadal synchronizowane. Jeśli połączenie się nie udało,
detektor przekazuje obiekt błędu wskazujący przyczynę błędu.
Swift
do { try await ref.child("users").child(user.uid).setValue(["username": username]) print("Data saved successfully!") } catch { print("Data could not be saved: \(error).") }
Objective-C
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { if (error) { NSLog(@"Data could not be saved: %@", error); } else { NSLog(@"Data saved successfully."); } }];
Usuń dane
Najprostszym sposobem usunięcia danych jest wywołanie funkcji removeValue
w odniesieniu do
lokalizacji danych.
Możesz również usunąć, określając nil
jako wartość innego zapisu
operacji, na przykład setValue
lub updateChildValues
. Tej metody możesz używać,
za pomocą updateChildValues
, aby usunąć wiele elementów podrzędnych w pojedynczym wywołaniu interfejsu API.
Odłącz detektory
Obserwatorzy nie zatrzymują automatycznie synchronizacji danych, gdy opuścisz stronę
ViewController
Jeśli obserwator nie zostanie prawidłowo usunięty, będzie nadal synchronizować się
do pamięci lokalnej. Jeśli obserwator nie jest już potrzebny, usuń go, przesuwając
powiązany element FIRDatabaseHandle
z metodą removeObserverWithHandle
.
Gdy dodasz blok wywołania zwrotnego do pliku referencyjnego, zwrócony zostanie element FIRDatabaseHandle
.
Za pomocą tych uchwytów można usunąć blok wywołania zwrotnego.
Jeśli do odwołania do bazy danych dodano wielu detektorów, każdy z nich jest wywoływany po wystąpieniu zdarzenia. Aby zatrzymać synchronizację danych w tej lokalizacji, musisz usunąć wszystkich obserwatorów w tej lokalizacji, wywołując metodę removeAllObservers
.
Wywoływanie removeObserverWithHandle
lub removeAllObservers
przez słuchacza
nie mogą automatycznie usuwać detektorów zarejestrowanych w węzłach podrzędnych; musisz też
śledzić te odniesienia lub nicki i je usunąć.
Zapisywanie danych jako transakcji
Podczas pracy z danymi, które mogły zostać uszkodzone w wyniku równoczesnej pracy takich jak liczniki przyrostowe, możesz użyć funkcji operacji transakcji. Należy przekazać tej operacji dwa argumenty: funkcję aktualizacji i opcjonalny zakończenia. Funkcja update przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać.
Na przykład w przypadku aplikacji do prowadzenia bloga społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdką i usuwanie tej oceny oraz śledzenie, ile gwiazdek otrzymał dany post.
Swift
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in if var post = currentData.value as? [String: AnyObject], let uid = Auth.auth().currentUser?.uid { var stars: [String: Bool] stars = post["stars"] as? [String: Bool] ?? [:] var starCount = post["starCount"] as? Int ?? 0 if let _ = stars[uid] { // Unstar the post and remove self from stars starCount -= 1 stars.removeValue(forKey: uid) } else { // Star the post and add self to stars starCount += 1 stars[uid] = true } post["starCount"] = starCount as AnyObject? post["stars"] = stars as AnyObject? // Set value and report transaction success currentData.value = post return TransactionResult.success(withValue: currentData) } return TransactionResult.success(withValue: currentData) }) { error, committed, snapshot in if let error = error { print(error.localizedDescription) } }
Objective-C
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) { NSMutableDictionary *post = currentData.value; if (!post || [post isEqual:[NSNull null]]) { return [FIRTransactionResult successWithValue:currentData]; } NSMutableDictionary *stars = post[@"stars"]; if (!stars) { stars = [[NSMutableDictionary alloc] initWithCapacity:1]; } NSString *uid = [FIRAuth auth].currentUser.uid; int starCount = [post[@"starCount"] intValue]; if (stars[uid]) { // Unstar the post and remove self from stars starCount--; [stars removeObjectForKey:uid]; } else { // Star the post and add self to stars starCount++; stars[uid] = @YES; } post[@"stars"] = stars; post[@"starCount"] = @(starCount); // Set value and report transaction success currentData.value = post; return [FIRTransactionResult successWithValue:currentData]; } andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) { // Transaction completed if (error) { NSLog(@"%@", error.localizedDescription); } }];
Użycie transakcji zapobiega nieprawidłowemu liczeniu gwiazdek w przypadku większej liczby
Użytkownicy oznaczali gwiazdką tego samego posta w tym samym czasie lub klient miał nieaktualne dane.
wartość zawarta w klasie FIRMutableData
jest początkowo ostatnią ostatnią klasą klienta
znanej wartości ścieżki lub nil
, jeśli jej nie ma. Serwer porównuje
bieżącej wartości i akceptuje transakcję, jeśli
pasują do wartości, czy też ją odrzuca. W przypadku odrzucenia transakcji serwer zwraca
bieżącą wartość klientowi, co powoduje ponowne uruchomienie transakcji
zaktualizowanej wartości. Powtarza się, dopóki transakcja nie zostanie zaakceptowana lub nie pojawi się zbyt wiele
wszystkich prób.
Atomic przyrosty po stronie serwera
W tym przypadku do bazy danych zapisujemy 2 wartości: identyfikator użytkownika, który oznaczył post gwiazdką lub odznaczył go gwiazdką, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy, że użytkownik oznaczył post jako ulubiony, możemy użyć operacji atomowej zwiększania zamiast transakcji.
Swift
let updates = [ "posts/\(postID)/stars/\(userID)": true, "posts/\(postID)/starCount": ServerValue.increment(1), "user-posts/\(postID)/stars/\(userID)": true, "user-posts/\(postID)/starCount": ServerValue.increment(1) ] as [String : Any] Database.database().reference().updateChildValues(updates)
Objective-C
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1], [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]}; [[[FIRDatabase database] reference] updateChildValues:updates];
Ten kod nie korzysta z operacji transakcji, więc nie pobiera automatycznie w przypadku wystąpienia konfliktu aktualizacji uruchom ponownie system. Ponieważ jednak operacja przyrostu odbywa się bezpośrednio na serwerze bazy danych, nie występuje konflikt.
Jeśli chcesz wykrywać i odrzucać konflikty dotyczące aplikacji, na przykład w postach, które zostały już wcześniej oznaczone gwiazdką, należy napisać niestandardowy dla konkretnego przypadku użycia.
Praca z danymi w trybie offline
Jeśli klient utraci połączenie z siecią, Twoja aplikacja będzie nadal działać prawidłowo.
Każdy klient połączony z bazą danych Firebase ma własną wersję wewnętrzną wszystkich aktywnych danych. Podczas zapisywania danych są one zapisywane w tej wersji lokalnej . Klient Firebase następnie synchronizuje te dane ze zdalną bazą danych. z serwerami Google i innymi klientami w ramach „najlepszych starań”, podstaw.
W rezultacie wszystkie zapisy w bazie danych wywołują zdarzenia lokalne natychmiast, przed jakiekolwiek dane są zapisywane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnień w sieci lub połączenia.
Po ponownym nawiązaniu połączenia aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera, bez konieczności pisania kodu niestandardowego.
Więcej informacji o zachowaniu użytkowników offline Więcej informacji o możliwościach pracy online i offline