Odczyt i zapis danych na platformach Apple

(Opcjonalnie) Tworzenie prototypów i testowanie za pomocą Firebase Local Emulator Suite

Zanim omówimy, jak aplikacja odczytuje i zapisuje dane w Realtime Database, przedstawimy zestaw narzędzi, których możesz używać 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 backendem, możliwość pracy lokalnej bez wdrażania usług na żywo może być świetnym rozwiązaniem.

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

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

  1. Dodanie wiersza kodu do konfiguracji testowej aplikacji, aby połączyć ją z emulatorem.
  2. z katalogu głównego projektu lokalnego, uruchamiając firebase emulators:start.
  3. Wywoływanie z kodu prototypu aplikacji za pomocą pakietu SDK platformy Realtime Database lub interfejsu API REST Realtime Database.

Dostępny jest szczegółowy przewodnik dotyczący Realtime Database i Cloud Functions. Warto też zapoznać się z Local Emulator Suite wprowadzeniem.

Uzyskiwanie obiektu FIRDatabaseReference

Aby odczytywać i zapisywać dane w bazie danych, potrzebujesz instancji FIRDatabaseReference:

Swift

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

ref = Database.database().reference()

Objective-C

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 Databasereferencji i pobierane przez dołączenie do niej asynchronicznego detektora. Słuchacz jest wywoływany raz w przypadku stanu początkowego danych i ponownie za każdym razem, gdy dane ulegną zmianie.

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć setValue, aby zapisać dane w określonym odwołaniu, zastępując wszystkie istniejące dane w tej ścieżce. Za pomocą tej metody możesz:

  • Typy kart odpowiadające dostępnym typom JSON w ten sposób:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Możesz na przykład dodać użytkownika z adresem setValue w ten sposób:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 klipu aplikacji.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

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

Swift

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

Objective-C

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

Odczytywanie danych

Odczytywanie danych przez nasłuchiwanie zdarzeń wartości

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

Typ zdarzenia Typowe zastosowanie
FIRDataEventTypeValue Odczytywanie i nasłuchiwanie zmian w całej zawartości ścieżki.

Za pomocą zdarzenia FIRDataEventTypeValue możesz odczytać dane w określonej ścieżce w momencie wystąpienia zdarzenia. Ta metoda jest wywoływana raz po dołączeniu odbiornika i ponownie za każdym razem, gdy zmienią się dane, w tym dane podrzędne. Funkcja zwrotna zdarzenia otrzymuje obiekt snapshot zawierający wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, podsumowanie zwróci wartość false, gdy wywołasz funkcję exists(), oraz wartość nil, gdy odczytasz jego właściwość value.

Poniższy przykład pokazuje aplikację do blogowania społecznościowego, która pobiera z bazy danych szczegóły posta:

Swift

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

Objective-C

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

Odbiornik otrzymuje FIRDataSnapshot, które zawiera dane z określonego miejsca w bazie danych w momencie wystąpienia zdarzenia w jego właściwości value. Możesz przypisać wartości do odpowiedniego typu natywnego, np. NSDictionary. Jeśli w lokalizacji nie ma danych, wartość value wynosi nil.

Odczytywanie danych tylko raz

Odczytanie danych za pomocą metody getData()

Pakiet SDK jest przeznaczony do zarządzania interakcjami z serwerami baz danych niezależnie od tego, czy aplikacja jest online czy offline.

Ogólnie rzecz biorąc, do odczytywania danych i otrzymywania powiadomień o aktualizacjach danych z backendu należy używać opisanych powyżej technik zdarzeń wartości. Te techniki zmniejszają zużycie danych i koszty, a także są zoptymalizowane pod kątem zapewnienia użytkownikom jak najlepszych wrażeń podczas przechodzenia w tryb online i offline.

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

Ten przykład pokazuje, jak jednorazowo pobrać z bazy danych publiczną nazwę użytkownika:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 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życie getData() może zwiększyć wykorzystanie przepustowości i spowodować utratę wydajności. Można temu zapobiec, używając odbiornika w czasie rzeczywistym, jak pokazano powyżej.

Jednorazowe odczytywanie danych za pomocą obserwatora

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

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

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 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 określonych 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ę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie instancje tych danych za pomocą zwielokrotnienia wyjściowego danych. Na przykład aplikacja do blogowania społecznościowego może chcieć utworzyć posta i jednocześnie zaktualizować go w kanale ostatniej aktywności i w kanale aktywności użytkownika, który go opublikował. W tym celu aplikacja do blogowania używa kodu takiego jak ten:

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 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 używamy funkcji childByAutoId, aby utworzyć post w węźle zawierającym posty wszystkich użytkowników w lokalizacji /posts/$postid, i jednocześnie pobrać klucz za pomocą funkcji getKey(). Klucz może być następnie użyty do utworzenia drugiego wpisu w postach użytkownika na stronie /user-posts/$userid/$postid.

Korzystając z tych ścieżek, możesz jednocześnie aktualizować wiele lokalizacji w drzewie JSON za pomocą jednego wywołania updateChildValues. Przykładem może być utworzenie nowego posta w obu lokalizacjach. Jednoczesne aktualizacje przeprowadzane w ten sposób są niepodzielne: albo wszystkie się udają, albo wszystkie się nie udają.

Dodawanie bloku ukończenia

Jeśli chcesz wiedzieć, kiedy dane zostały zatwierdzone, możesz dodać blok zakończenia. Zarówno setValue, jak i updateChildValues przyjmują opcjonalny blok uzupełniania, który jest wywoływany, gdy zapis zostanie zatwierdzony w bazie danych. Ten odbiornik może być przydatny do śledzenia, które dane zostały zapisane, a które są nadal synchronizowane. Jeśli wywołanie się nie powiedzie, odbiornik otrzyma obiekt błędu wskazujący przyczynę niepowodzenia.

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 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 usunięcia danych jest wywołanie funkcji removeValue na odwołaniu do lokalizacji tych danych.

Możesz też usunąć element, podając nil jako wartość innej operacji zapisu, np. setValue lub updateChildValues. Możesz użyć tej techniki z updateChildValues, aby usunąć wiele elementów podrzędnych w ramach jednego wywołania interfejsu API.

Odłączanie detektorów

Gdy opuścisz ViewController, obserwatorzy nie przestaną automatycznie synchronizować danych. Jeśli obserwator nie zostanie prawidłowo usunięty, będzie nadal synchronizować dane z pamięcią lokalną. Gdy obserwator nie jest już potrzebny, usuń go, przekazując powiązany FIRDatabaseHandle do metody removeObserverWithHandle.

Gdy dodasz blok wywołania zwrotnego do odwołania, zwracana jest wartość FIRDatabaseHandle. Za pomocą tych uchwytów możesz usunąć blok wywołania zwrotnego.

Jeśli do odwołania do bazy danych dodano wiele detektorów, każdy z nich jest wywoływany po wystąpieniu zdarzenia. Aby zatrzymać synchronizację danych w danej lokalizacji, musisz usunąć wszystkich obserwatorów w tej lokalizacji, wywołując metodę removeAllObservers.

Wywołanie funkcji removeObserverWithHandle lub removeAllObservers w przypadku odbiornika nie powoduje automatycznego usunięcia odbiorników zarejestrowanych w jego węzłach podrzędnych. Aby je usunąć, musisz też śledzić te odwołania lub uchwyty.

Zapisywanie danych jako transakcji

W przypadku danych, które mogą zostać uszkodzone przez równoczesne modyfikacje, np. liczników przyrostowych, możesz użyć operacji transakcji. Ta operacja wymaga podania 2 argumentów: funkcji aktualizacji i opcjonalnego wywołania zwrotnego zakończenia. Funkcja aktualizacji przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać.

Na przykład w przykładowej aplikacji do blogowania społecznościowego możesz umożliwić użytkownikom oznaczanie postów gwiazdką i cofanie tego oznaczenia oraz śledzenie liczby gwiazdek, które otrzymał post, w ten sposób:

Swift

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

Użycie transakcji zapobiega nieprawidłowemu zliczaniu gwiazdek, jeśli wielu użytkowników doda gwiazdkę do tego samego posta w tym samym czasie lub jeśli klient miał nieaktualne dane. Wartość zawarta w klasie FIRMutableData to początkowo ostatnia znana klientowi wartość ścieżki lub nil, jeśli nie ma takiej wartości. Serwer porównuje wartość początkową z wartością bieżącą i akceptuje transakcję, jeśli wartości są zgodne, lub ją odrzuca. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość do klienta, który ponownie uruchomi transakcję ze zaktualizowaną wartością. Powtarza się to, dopóki transakcja nie zostanie zaakceptowana lub nie zostanie podjętych zbyt wiele prób.

Atomowe zwiększanie wartości po stronie serwera

W powyższym przypadku użycia zapisujemy w bazie danych 2 wartości: identyfikator użytkownika, który dodaje lub usuwa gwiazdkę, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy już, że użytkownik oznaczył post gwiazdką, możemy użyć operacji przyrostu niepodzielnego zamiast transakcji.

Swift

Uwaga: ta usługa Firebase nie jest dostępna w przypadku 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 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 uruchamiany ponownie w przypadku sprzecznej aktualizacji. Jednak ponieważ operacja zwiększania odbywa się bezpośrednio na serwerze bazy danych, nie ma możliwości wystąpienia konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty związane z aplikacją, np. gdy użytkownik oznaczy gwiazdką post, który już wcześniej oznaczył, napisz niestandardowe reguły zabezpieczeń dla tego przypadku użycia.

Praca z danymi offline

Jeśli klient utraci połączenie sieciowe, aplikacja będzie nadal działać prawidłowo.

Każdy klient połączony z bazą danych Firebase ma własną wewnętrzną wersję aktywnych danych. Gdy dane są zapisywane, najpierw trafiają do tej lokalnej wersji. Klient Firebase synchronizuje te dane z serwerami zdalnej bazy danych i innymi klientami w miarę możliwości.

W rezultacie wszystkie zapisy w bazie danych natychmiast wywołują zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnienia 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 niestandardowego kodu.

Więcej informacji o zachowaniach offline znajdziesz w artykule Więcej informacji o funkcjach online i offline.

Następne kroki