Odczyt i zapis danych na platformach Apple

(Opcjonalnie) Prototypowanie i testowanie za pomocą Firebase Local Emulator Suite

Zanim omówimy, jak aplikacja odczytuje i zapisze dane w Realtime Database, 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 różne modele danych, optymalizujesz reguły bezpieczeństwa lub szukasz najbardziej opłacalnego sposobu interakcji z back-endem, warto pracować lokalnie bez wdrażania usług na żywo.

Emulator Realtime Database jest częścią Local Emulator Suite, która umożliwia aplikacji interakcję z emulowaną zawartością i konfiguracją bazy danych, a także opcjonalnie z emulowanymi zasobami projektu (funkcjami, innymi bazami danych i regułami zabezpieczeń).

Korzystanie z emulatora Realtime Database wymaga wykonania kilku czynności:

  1. Dodanie linii kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
  2. W katalogu głównym lokalnego katalogu projektu uruchom firebase emulators:start.
  3. Wykonywanie wywołań z prototypowego kodu aplikacji za pomocą platformy Realtime Database SDK lub interfejsu API REST Realtime Database.

Dostępny jest szczegółowy przebieg procesu dotyczący funkcji Realtime DatabaseCloud Functions. Zapoznaj się też z Local Emulator Suitewprowadzeniem.

Pobieranie obiektu FIRDatabaseReference

Aby odczytywać lub zapisywać dane z bazy danych, potrzebujesz instancji FIRDatabaseReference:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

Zapisywanie danych

W tym dokumencie znajdziesz podstawowe informacje o odczytywaniu i zapisywaniu danych Firebase.

Dane Firebase są zapisywane w elementach referencyjnych Database i pobierane przez dołączenie do nich asynchronicznego odbiornika. Listener jest uruchamiany raz w przypadku początkowego stanu danych i po raz kolejny, gdy dane ulegną zmianie.

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć funkcji setValue, aby zapisać dane w określonej referencji, zastępując wszystkie istniejące dane na tej ścieżce. Dzięki tej metodzie możesz:

  • Typy przepustki odpowiadające dostępnym typom JSON:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Użytkownika z polem setValue możesz dodać w ten sposób:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

Użycie w ten sposób polecenia setValue spowoduje zastąpienie danych w wybranej lokalizacji, w tym wszystkich węzłów podrzędnych. Nadal jednak możesz zaktualizować element podrzędny bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie ich profili, możesz zaktualizować nazwę użytkownika w ten sposób:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Odczytywanie danych

Odczytywanie danych przez nasłuchiwanie zdarzeń wartości

Aby odczytywać dane na ścieżce i nasłuchiwać zmian, użyj funkcji observeEventType:withBlock w FIRDatabaseReference, aby obserwować zdarzenia FIRDataEventTypeValue.

Typ zdarzenia Typowe zastosowanie
FIRDataEventTypeValue czytać i słuchać zmian w całej zawartości ścieżki;

Zdarzenia FIRDataEventTypeValue możesz użyć do odczytania danych na danej ścieżce w stanie, w jakim były w momencie wystąpienia zdarzenia. Ta metoda jest wywoływana raz, gdy listener zostanie dołączony, oraz za każdym razem, gdy dane, w tym elementy podrzędne, ulegną zmianie. Do funkcji wywołania zwrotnego zdarzenia przekazywany jest obiekt snapshot zawierający wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma żadnych danych, podsumowanie zwróci wartość false, gdy wywołasz funkcję exists(), i nil, gdy odczytasz jej właściwość value.

Ten przykład pokazuje aplikację do blogowania społecznościowego, która pobiera szczegóły wpisu z bazy danych:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Listener otrzymuje obiekt FIRDataSnapshot, który zawiera dane z określonej lokalizacji w bazie danych w momencie zdarzenia w usłudze value. Wartości możesz przypisać do odpowiedniego typu natywnego, np. NSDictionary. Jeśli w danej lokalizacji nie ma żadnych danych, wartość value wynosi nil.

jednokrotny odczyt danych,

Jednokrotny odczyt za pomocą funkcji getData()

Pakiet SDK umożliwia zarządzanie interakcjami z serwerami baz danych niezależnie od tego, czy aplikacja jest online, czy offline.

Ogólnie zalecamy używanie opisanych powyżej metod zdarzeń wartościowych do odczytywania danych, aby otrzymywać powiadomienia o ich aktualizacjach z back-endu. Te techniki zmniejszają wykorzystanie i obciążenia oraz są zoptymalizowane pod kątem zapewnienia użytkownikom jak najlepszego wrażenia podczas korzystania z usługi online i offline.

Jeśli dane są potrzebne tylko raz, możesz użyć funkcji getData(), aby uzyskać podsumowanie danych z bazy danych. Jeśli z jakiegokolwiek powodu getData() nie może zwrócić wartości serwera, klient sprawdzi pamięć podręczną lokalnego magazynu i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.

Ten przykład pokazuje, jak pojedynczo pobierać z bazy danych publiczny identyfikator użytkownika:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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 funkcji 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.

jednokrotny odczyt danych z obserwatorem,

W niektórych przypadkach możesz chcieć, aby wartość z lokalnej pamięci podręcznej była zwracana natychmiast zamiast sprawdzać zaktualizowaną wartość na serwerze. W takich przypadkach możesz użyć polecenia observeSingleEventOfType, aby natychmiast pobrać dane z lokalnego dysku podręcznego.

Jest to przydatne w przypadku danych, które trzeba wczytać tylko raz i które nie powinny się często zmieniać ani wymagać aktywnego słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do wczytania profilu użytkownika, gdy ten zaczyna pisać nowy post:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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 i usuwanie danych

Aktualizowanie konkretnych pól

Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych bez nadpisywania innych węzłów podrzędnych, użyj metody updateChildValues.

Podczas wywoływania funkcji updateChildValues możesz aktualizować wartości podrzędne niższego poziomu, podając ścieżkę klucza. Jeśli dane są przechowywane w kilku lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie wystąpienia tych danych za pomocą rozgałęzienia danych. Na przykład aplikacja do blogowania społecznościowego może chcieć utworzyć post i jednocześnie zaktualizować go w strumieniach aktywności ostatnich i użytkownika, który opublikował post. W tym celu aplikacja do prowadzenia bloga używa kodu podobnego do tego:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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 funkcja childByAutoId służy do tworzenia postu w węźle zawierającym posty wszystkich użytkowników w węźle /posts/$postid i jednoczesnego pobierania klucza za pomocą funkcji getKey(). Klucz można następnie wykorzystać do utworzenia drugiego wpisu w postach użytkownika na stronie /user-posts/$userid/$postid.

Dzięki tym ścieżkom możesz przeprowadzać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania funkcji updateChildValues, jak w tym przykładzie, w którym tworzy nowy post w obu lokalizacjach. Jednoczesne aktualizacje w ten sposób są atomowe: albo wszystkie się powiodą, albo wszystkie się nie powiodą.

Dodawanie bloku zakończenia

Jeśli chcesz wiedzieć, kiedy dane zostały zapisane, możesz dodać blok zakończenia. Zarówno setValue, jak i updateChildValues przyjmują opcjonalny blok zakończenia, który jest wywoływany po zaakceptowaniu zapisu w bazie danych. Ten odbiorca może być przydatny do śledzenia, które dane zostały zapisane, a które są nadal synchronizowane. Jeśli wywołanie zakończy się niepowodzeniem, do odbiorcy zostanie przekazany obiekt błędu wskazujący, dlaczego wystąpił błąd.

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
[[[_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 usuwania danych jest wywołanie funkcji removeValue z odniesieniem do lokalizacji tych danych.

Możesz też usunąć element, podając wartość nil dla innej operacji zapisu, takiej jak setValue lub updateChildValues. Możesz użyć tej metody z updateChildValues, aby usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.

Odłączanie słuchaczy

Obserwatorzy nie przerywają automatycznie synchronizowania danych, gdy opuszczasz ViewController. Jeśli obserwator nie zostanie prawidłowo usunięty, nadal będzie synchronizować dane z pamięci lokalnej. Gdy obserwator nie jest już potrzebny, usuń go, przekazując powiązany obiekt FIRDatabaseHandle do metody removeObserverWithHandle.

Gdy dodasz do odwołania blok wywołania zwrotnego, zwrócony zostanie element FIRDatabaseHandle. Za pomocą tych uchwytów możesz usunąć blokadę 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łanie metody removeObserverWithHandle lub removeAllObservers na słuchaczu nie powoduje automatycznego usunięcia słuchaczy zarejestrowanych w podrzędnych węzłach. Musisz też śledzić te odwołania lub uchwyty, aby je usunąć.

Zapisywanie danych jako transakcji

Podczas pracy z danymi, które mogą zostać uszkodzone przez równoległe modyfikacje, np. z licznikami przyrostowymi, możesz użyć operacji transakcji. Ta operacja wymaga podania 2 argumentów: funkcji aktualizacji i opcjonalnego wywołania zwrotnego po zakończeniu. 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. W tym celu:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
[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);
  }
}];

Transakcja zapobiega nieprawidłowemu zliczaniu gwiazdek, jeśli więcej użytkowników oznaczy gwiazdką ten sam post w tym samym czasie lub jeśli klient ma nieaktualne dane. Wartość zawarta w klasie FIRMutableData to początkowo ostatnia znana wartość ścieżki klienta lub nil, jeśli takiej wartości nie ma. Serwer porównuje wartość początkową z jej bieżącą wartością i akceptuje transakcję, jeśli wartości są zgodne, lub odrzuca ją. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość klientowi, który ponownie wykona transakcję z aktualną wartością. Czynność ta jest powtarzana, dopóki transakcja nie zostanie zaakceptowana lub nie zostanie podjęta zbyt duża liczba prób.

Atomowe inkrementy po stronie serwera

W tym przypadku do bazy danych zapisujemy 2 wartości: identyfikator użytkownika, który oznaczył post gwiazdką lub odznaczył go z 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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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

Uwaga: ta usługa Firebase nie jest dostępna w przypadku docelowego klipu aplikacji.
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 jest automatycznie ponownie uruchamiany, jeśli wystąpiła aktualizacja sprzeczna. Operacja zwiększania wartości występuje jednak bezpośrednio na serwerze bazy danych, więc nie ma możliwości wystąpienia konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty związane z konkretną aplikacją, np. gdy użytkownik wyróżnia wpis, który wyróżnił już wcześniej, na potrzeby tego przypadku użycia napisz niestandardowe reguły bezpieczeństwa.

Praca z danymi 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 utrzymuje własną wersję wewnętrzną wszystkich aktywnych danych. Dane są najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami bazy danych i z innymi klientami według zasady „najlepszego wysiłku”.

W rezultacie wszystkie zapisy w bazie danych powodują natychmiastowe wywołanie zdarzeń lokalnych, zanim jakiekolwiek dane zostaną zapisane 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 działaniu offline znajdziesz w artykule Więcej informacji o możliwościach online i offline.

Następne kroki