Odczytuj i zapisuj dane na platformach Apple

(Opcjonalnie) Stwórz prototyp i przetestuj za pomocą pakietu Firebase Local Emulator Suite

Zanim porozmawiamy o tym, jak Twoja aplikacja odczytuje i zapisuje w bazie danych Realtime Database, przedstawmy zestaw narzędzi, których możesz użyć do prototypowania i testowania funkcjonalności bazy danych Realtime: Firebase Local Emulator Suite. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły bezpieczeństwa lub szukasz najbardziej opłacalnego sposobu interakcji z zapleczem, możliwość pracy lokalnej bez wdrażania usług na żywo może być świetnym pomysłem.

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

Korzystanie z emulatora bazy danych czasu rzeczywistego obejmuje tylko kilka kroków:

  1. Dodanie wiersza kodu do konfiguracji testowej aplikacji w celu nawiązania połączenia z emulatorem.
  2. Z katalogu głównego lokalnego katalogu projektu uruchom firebase emulators:start .
  3. Wykonywanie wywołań z kodu prototypu aplikacji przy użyciu standardowego pakietu SDK platformy Realtime Database lub interfejsu API REST bazy danych Realtime.

Dostępny jest szczegółowy przewodnik dotyczący bazy danych czasu rzeczywistego i funkcji chmury . Powinieneś także zapoznać się ze wstępem do pakietu Local Emulator Suite .

Uzyskaj FIRDatabaseReference

Aby odczytać lub zapisać dane z bazy danych, potrzebujesz instancji FIRDatabaseReference :

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
var ref: DatabaseReference!

ref = Database.database().reference()

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Zapisz dane

W tym dokumencie opisano podstawy odczytywania i zapisywania danych Firebase.

Dane Firebase są zapisywane w odwołaniu do Database i pobierane poprzez dołączenie asynchronicznego odbiornika do odniesienia. Odbiornik jest uruchamiany raz dla stanu początkowego danych i ponownie za każdym razem, gdy dane się zmieniają.

Podstawowe operacje zapisu

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

  • Typy przebiegów odpowiadające dostępnym typom JSON w następujący sposób:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Na przykład możesz dodać użytkownika za pomocą setValue w następujący sposób:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
self.ref.child("users").child(user.uid).setValue(["username": username])

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

Użycie setValue w ten sposób powoduje nadpisanie danych w określonej lokalizacji, łącznie ze wszystkimi węzłami podrzędnymi. Jednak nadal możesz zaktualizować element podrzędny bez przepisywania całego obiektu. Jeśli chcesz umożliwić użytkownikom aktualizację swoich profili, możesz zaktualizować nazwę użytkownika w następujący sposób:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
self.ref.child("users/\(user.uid)/username").setValue(username)

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Przeczytaj dane

Odczytuj dane, nasłuchując zdarzeń wartościowych

Aby odczytać dane na ścieżce i nasłuchiwać zmian, użyj observeEventType:withBlock elementu FIRDatabaseReference w celu obserwacji zdarzeń FIRDataEventTypeValue .

Typ wydarzenia Typowe użycie
FIRDataEventTypeValue Czytaj i słuchaj zmian w całej zawartości ścieżki.

Za pomocą zdarzenia FIRDataEventTypeValue można odczytać dane z danej ścieżki, takie jakie istnieją w momencie zdarzenia. Ta metoda jest wyzwalana raz, gdy słuchacz jest podłączony, i ponownie za każdym razem, gdy dane, w tym wszystkie elementy podrzędne, ulegną zmianie. Do wywołania zwrotnego zdarzenia przekazywana jest snapshot zawierająca wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, migawka zwróci false po wywołaniu funkcji exists() i nil po odczytaniu jej właściwości value .

Poniższy przykład ilustruje aplikację do blogowania społecznościowego pobierającą szczegóły wpisu z bazy danych:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Odbiornik odbiera FIRDataSnapshot , który zawiera dane w określonej lokalizacji w bazie danych w momencie zdarzenia w swojej właściwości value . Możesz przypisać wartości do odpowiedniego typu natywnego, takiego jak NSDictionary . Jeśli w lokalizacji nie istnieją żadne dane, value wynosi nil .

Przeczytaj dane raz

Przeczytaj raz, używając getData()

Zestaw SDK służy do zarządzania interakcjami z serwerami baz danych, niezależnie od tego, czy aplikacja jest w trybie online, czy offline.

Ogólnie rzecz biorąc, należy używać opisanych powyżej technik zdarzeń wartości do odczytywania danych i otrzymywania powiadomień o aktualizacjach danych z zaplecza. Techniki te ograniczają wykorzystanie i rozliczenia oraz są zoptymalizowane, aby zapewnić użytkownikom najlepsze doświadczenia podczas korzystania z Internetu i offline.

Jeśli potrzebujesz danych tylko raz, możesz użyć getData() w celu uzyskania migawki danych z bazy danych. Jeśli z jakiegoś powodu funkcja getData() nie może zwrócić wartości serwera, klient sprawdzi lokalną pamięć podręczną i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.

Poniższy przykład ilustruje jednorazowe pobranie z bazy danych publicznej nazwy użytkownika:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie aplikacji.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 funkcji getData() może zwiększyć wykorzystanie przepustowości i prowadzić do utraty wydajności, czemu można zapobiec, korzystając z odbiornika działającego w czasie rzeczywistym, jak pokazano powyżej.

Odczytaj dane raz z obserwatorem

W niektórych przypadkach możesz chcieć natychmiastowego zwrócenia wartości z lokalnej pamięci podręcznej, zamiast sprawdzać, czy na serwerze jest zaktualizowana wartość. W takich przypadkach można użyć observeSingleEventOfType , aby natychmiast pobrać dane z pamięci podręcznej dysku lokalnego.

Jest to przydatne w przypadku danych, które należy załadować tylko raz i nie oczekuje się, że będą 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 ładowania profilu użytkownika, gdy rozpoczyna on tworzenie nowego posta:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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)
}

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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);
}];

Aktualizacja lub usuwanie danych

Zaktualizuj określone pola

Aby jednocześnie pisać do określonych węzłów podrzędnych węzła bez nadpisywania innych węzłów podrzędnych, należy użyć metody updateChildValues .

Wywołując updateChildValues ​​, możesz zaktualizować wartości podrzędne niższego poziomu, określając ścieżkę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach w celu lepszego skalowania, możesz zaktualizować wszystkie wystąpienia tych danych, korzystając z rozdzielania danych . Na przykład aplikacja do blogowania społecznościowego może chcieć utworzyć post i jednocześnie zaktualizować go do kanału ostatniej aktywności i kanału aktywności użytkownika zamieszczającego post. W tym celu aplikacja do blogowania używa następującego kodu:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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)

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 zastosowano childByAutoId do utworzenia posta w węźle zawierającego posty dla wszystkich użytkowników w /posts/$postid i jednoczesnego pobrania klucza za pomocą getKey() . Klucza można następnie użyć do utworzenia drugiego wpisu w postach użytkownika pod adresem /user-posts/$userid/$postid .

Korzystając z tych ścieżek, możesz wykonywać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania updateChildValues ​​, na przykład jak w tym przykładzie tworzy się nowy wpis w obu lokalizacjach. Jednoczesne aktualizacje wykonane w ten sposób mają charakter niepodzielny: albo wszystkie aktualizacje się powiodą, albo wszystkie aktualizacje nie powiodą się.

Dodaj blok ukończenia

Jeśli chcesz wiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać blok uzupełniający. 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 nadal są synchronizowane. Jeśli wywołanie nie powiedzie się, do słuchacza zostanie przekazany obiekt błędu wskazujący przyczynę niepowodzenia.

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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).")
}

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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.");
  }
}];

Usunąć dane

Najprostszym sposobem usunięcia danych jest wywołanie metody removeValue w odniesieniu do lokalizacji tych danych.

Można również usunąć, określając nil jako wartość innej operacji zapisu, takiej jak setValue lub updateChildValues ​​. Możesz użyć tej techniki z updateChildValues , aby usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.

Odłącz słuchaczy

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

Po dodaniu bloku wywołania zwrotnego do odwołania zwracany jest FIRDatabaseHandle . Uchwyty te można wykorzystać do usunięcia bloku wywołania zwrotnego.

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

Wywołanie metody removeObserverWithHandle lub removeAllObservers na odbiorniku nie powoduje automatycznego usunięcia odbiorników zarejestrowanych w jego węzłach podrzędnych; musisz także śledzić te odniesienia lub uchwyty, aby je usunąć.

Zapisz dane jako transakcje

Podczas pracy z danymi, które mogą zostać uszkodzone przez jednoczesne modyfikacje, takie jak liczniki przyrostowe, można użyć operacji transakcyjnej . Podajesz tej operacji dwa argumenty: funkcję aktualizacji i opcjonalne wywołanie zwrotne zakończenia. Funkcja aktualizacji przyjmuje bieżący stan danych jako argument i zwraca nowy żądany stan, który chcesz zapisać.

Na przykład w przykładowej aplikacji do blogowania społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdkami i usuwanie ich gwiazdką oraz śledzenie liczby gwiazdek, które otrzymał post, w następujący sposób:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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)
  }
}

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 zliczeniu gwiazdek, jeśli wielu użytkowników oznacza ten sam post w tym samym czasie lub klient ma nieaktualne dane. Wartość zawarta w klasie FIRMutableData jest początkowo ostatnią znaną wartością ścieżki klienta lub nil , jeśli jej nie ma. Serwer porównuje wartość początkową z jej aktualną wartością i akceptuje transakcję, jeśli wartości się zgadzają, lub ją odrzuca. Jeżeli transakcja zostanie odrzucona, serwer zwraca aktualną wartość klientowi, który ponownie uruchamia transakcję ze zaktualizowaną wartością. Powtarza się to do momentu zaakceptowania transakcji lub podjęcia zbyt wielu prób.

Atomowe przyrosty po stronie serwera

W powyższym przypadku zapisujemy do bazy danych dwie wartości: identyfikator użytkownika, który gwiazdką/odznaczył posta gwiazdką oraz zwiększoną liczbę gwiazdek. Jeśli już wiemy, że użytkownik oznacza post gwiazdką, zamiast transakcji możemy zastosować operację przyrostu atomowego.

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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)

Cel C

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 wykorzystuje operacji transakcyjnej, więc nie jest automatycznie uruchamiany ponownie, jeśli wystąpi konflikt aktualizacji. Ponieważ jednak operacja inkrementacji odbywa się bezpośrednio na serwerze bazy danych, nie ma ryzyka wystąpienia konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty specyficzne dla aplikacji, np. oznaczenie użytkownika gwiazdką postu, który już wcześniej dodał, powinieneś napisać niestandardowe reguły bezpieczeństwa dla tego przypadku użycia.

Pracuj z danymi w trybie offline

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

Każdy klient podłączony do bazy danych Firebase utrzymuje własną wewnętrzną wersję wszelkich aktywnych danych. Kiedy dane są zapisywane, są najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami baz danych i innymi klientami, dokładając wszelkich starań.

W rezultacie wszystkie zapisy do bazy danych natychmiast wyzwalają zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że Twoja aplikacja pozostaje responsywna niezależnie od opóźnienia sieci lub łączności.

Po ponownym nawiązaniu połączenia aplikacja odbiera odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania żadnego niestandardowego kodu.

Więcej o zachowaniu w trybie offline porozmawiamy w artykule Dowiedz się więcej o możliwościach w trybie online i offline .

Następne kroki