Odczytywanie i zapisywanie danych na platformach Apple

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

Zanim omówimy sposób, w jaki Twoja aplikacja odczytuje i zapisuje w Bazie danych czasu rzeczywistego, przedstawmy zestaw narzędzi, których możesz użyć do prototypowania i testowania funkcjonalności Bazy danych czasu rzeczywistego: Pakiet emulatorów lokalnych Firebase. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły zabezpieczeń lub pracujesz nad znalezieniem 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ą lokalnego pakietu emulatorów, który umożliwia aplikacji interakcję z zawartością i konfiguracją emulowanej bazy danych, a także opcjonalnie z emulowanymi zasobami projektu (funkcje, inne bazy danych i reguły zabezpieczeń).

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

  1. Dodanie wiersza kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
  2. Z katalogu lokalnego katalogu projektu, uruchomiony firebase emulators:start .
  3. Wykonywanie wywołań z kodu prototypowego aplikacji przy użyciu zestawu SDK platformy Realtime Database jak zwykle lub przy użyciu interfejsu API REST Realtime Database.

Szczegółowy solucja udziałem Aktualizacje bazy danych i funkcji Chmura jest dostępna. Należy również spojrzeć na wstępie Local Emulator Suite .

Uzyskaj referencję bazy danych FIR

Do odczytu lub zapisu danych z bazy danych, trzeba instancję FIRDatabaseReference :

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej aplikacji.
var ref: DatabaseReference!

ref = Database.database().reference()

Cel C

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej aplikacji.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Zapisz dane

W tym dokumencie omówiono podstawy odczytywania i zapisywania danych Firebase.

Firebase dane są zapisywane do Database odniesienia i pobierane przez dołączenie asynchronicznego słuchacza do odniesienia. Odbiornik jest wyzwalany raz dla początkowego stanu danych i ponownie za każdym razem, gdy dane się zmieniają.

Podstawowe operacje zapisu

Dla podstawowych operacji zapisu, można użyć setValue zapisać dane do określonego odniesienia, zastępując istniejące dane na tej ścieżce. Możesz użyć tej metody do:

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

Na przykład, można dodać użytkownikowi setValue następująco:

Szybki

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

Cel C

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

Korzystanie setValue w ten sposób nadpisuje dane w określonym miejscu, w tym wszelkich węzłów potomnych. Jednak nadal możesz zaktualizować dziecko bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie ich profili, możesz zaktualizować nazwę użytkownika w następujący sposób:

Szybki

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

Cel C

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

Przeczytaj dane

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

Aby odczytać dane na ścieżce i słuchać zmian, użyj observeEventType:withBlock z FIRDatabaseReference obserwować FIRDataEventTypeValue wydarzenia.

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

Można użyć FIRDataEventTypeValue imprezę do odczytywania danych w danej ścieżce, jak istnieje w chwili zdarzenia. Ta metoda jest wywoływana raz, gdy odbiornik jest podłączony, i ponownie za każdym razem, gdy dane, w tym wszelkie elementy podrzędne, ulegną zmianie. Wywołanie zwrotne zdarzenia jest przekazywany do snapshot zawierający wszystkie dane w tym miejscu, w tym danych potomnych. Jeśli nie ma danych, snapshot zwróci false gdy dzwonisz exists() i nil kiedy czytasz swoją value nieruchomości.

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

Szybki

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

Cel C

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

Słuchacz otrzymuje FIRDataSnapshot który zawiera dane w określonej lokalizacji w bazie danych w czasie zdarzenia w jego value nieruchomości. Można przypisać wartości do odpowiedniego natywnego typu, takich jak NSDictionary . Jeśli dane nie istnieje w lokalizacji, value jest nil .

Przeczytaj dane raz

Przeczytaj raz za pomocą getData()

Zestaw SDK jest przeznaczony 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żyć opisanych powyżej technik zdarzeń wartości, aby odczytać dane i otrzymywać powiadomienia o aktualizacjach danych z zaplecza. Techniki te zmniejszają zużycie i rozliczenia oraz są zoptymalizowane, aby zapewnić użytkownikom jak najlepsze wrażenia podczas korzystania z Internetu i offline.

Jeśli potrzebne są dane tylko raz, można użyć getData() aby uzyskać zrzut danych z bazy danych. Jeśli z jakiegokolwiek powodu getData() nie jest w stanie zwrócić wartość serwera, klient będzie sondować lokalnej pamięci podręcznej pamięci i zwraca błąd, jeśli wartość jest nadal nie została znaleziona.

Poniższy przykład ilustruje jednorazowe pobieranie nazwy użytkownika widocznej publicznie z bazy danych:

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej aplikacji.
ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

Cel C

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 stosowanie getData() może zwiększyć wykorzystanie szerokości pasma i doprowadzić do utraty wydajności, którym można zapobiegać za pomocą detektora w czasie rzeczywistym, jak pokazano powyżej.

Przeczytaj dane raz z obserwatorem

W niektórych przypadkach możesz chcieć, aby wartość z lokalnej pamięci podręcznej została zwrócona natychmiast, zamiast sprawdzania zaktualizowanej wartości na serwerze. W takich przypadkach można użyć observeSingleEventOfType aby natychmiast uzyskać dane z lokalnej pamięci podręcznej dysku.

Jest to przydatne w przypadku danych, które trzeba załadować tylko raz i nie oczekuje się, że będą się często zmieniać lub wymagają aktywnego nasłuchiwania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do ładowania profilu użytkownika, gdy rozpoczyna on tworzenie nowego posta:

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 nie jest Firebase dostępny na klipie docelowej 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

Jednoczesnego zapisu do konkretnych dzieci węzła bez zastępowania innych węzłów potomnych, należy użyć updateChildValues metody.

Dzwoniąc updateChildValues , można zaktualizować wartości podrzędne niższego poziomu, określając ścieżkę do klucza. Jeśli dane są przechowywane w wielu miejscach, aby lepiej skalować, można zaktualizować wszystkie instancje tych danych z wykorzystaniem fan-out 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 publikującego. Aby to zrobić, aplikacja do blogowania używa takiego kodu:

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 nie jest Firebase dostępny na klipie docelowej 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żyto childByAutoId aby utworzyć wpis w węźle zawierającym stanowisk dla wszystkich użytkowników w /posts/$postid a jednocześnie odzyskać klucz z getKey() . Kluczem może być następnie wykorzystane do stworzenia drugiego wpisu postów użytkownika w /user-posts/$userid/$postid .

Korzystanie z tych ścieżek, można wykonywać jednoczesne aktualizacje do wielu lokalizacji w drzewie JSON z pojedynczym wywołaniu updateChildValues , takich jak jak ten przykład tworzy nowy wpis w obu lokalizacjach. Aktualizacje symultaniczne wykonane w ten sposób są atomowe: albo wszystkie aktualizacje powiodą się, albo wszystkie aktualizacje nie powiodą się.

Dodaj blok dokończenia

Jeśli chcesz wiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać blok uzupełniania. Zarówno setValue i updateChildValues wziąć opcjonalny blok zakończenia, która jest wywoływana, gdy zostało popełnione zapisu do bazy danych. Ten detektor może być przydatny do śledzenia, które dane zostały zapisane, a które są nadal synchronizowane. Jeśli wywołanie nie powiodło się, obiektowi nasłuchującemu jest przekazywany obiekt błędu wskazujący przyczynę niepowodzenia.

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej aplikacji.
ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

Cel C

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 removeValue w odniesieniu do lokalizacji tych danych.

Można również usunąć poprzez podanie nil jako wartość dla innej operacji zapisu, takich jak setValue lub updateChildValues . Możesz użyć tej techniki z updateChildValues usunąć wiele dzieci w jednym wywołaniu API.

Odłącz słuchaczy

Obserwatorzy nie automatycznie zatrzymać synchronizację danych po zostawić ViewController . Jeśli obserwator nie zostanie poprawnie usunięty, kontynuuje synchronizację danych z pamięcią lokalną. Gdy obserwator nie jest już potrzebny, należy go usunąć poprzez przepuszczenie powiązanych FIRDatabaseHandle do removeObserverWithHandle metody.

Po dodaniu bloku oddzwonienia do odniesienia, o FIRDatabaseHandle jest zwracana. Te uchwyty mogą służyć do usuwania bloku wywołania zwrotnego.

Jeśli do odwołania do bazy danych dodano wiele detektorów, każdy detektor jest wywoływany, gdy zostanie zgłoszone zdarzenie. W celu zatrzymania synchronizowania danych w tym miejscu, należy usunąć wszystkie obserwatorów w miejscu poprzez wywołanie removeAllObservers metody.

Wywołanie removeObserverWithHandle lub removeAllObservers na słuchacza nie powoduje automatycznego usunięcia detektory zarejestrowane na jego węzłów potomnych; musisz także śledzić te odniesienia lub uchwyty, aby je usunąć.

Zapisz dane jako transakcje

Podczas pracy z danymi, które mogłyby zostać uszkodzone przez współbieżnych modyfikacji, takich jak liczniki przyrostowe, można użyć operacji transakcji . 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 pożą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 i odznaczanie postów oraz śledzenie liczby gwiazdek, które otrzymał post w następujący sposób:

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 nie jest Firebase dostępny na klipie docelowej 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);
  }
}];

Korzystanie z transakcji zapobiega błędnym zliczaniu gwiazdek, jeśli wielu użytkowników oznacza ten sam post w tym samym czasie lub klient ma nieaktualne dane. Wartość zawarta w FIRMutableData klasy jest początkowo ostatnia znana wartość klienta na ścieżce, lub nil , jeśli nie ma. Serwer porównuje wartość początkową z wartością bieżącą i akceptuje transakcję, jeśli wartości są zgodne, lub ją odrzuca. W przypadku odrzucenia transakcji serwer zwraca bieżącą wartość do klienta, który ponownie uruchamia transakcję ze zaktualizowaną wartością. Powtarza się to do momentu zaakceptowania transakcji lub wykonania 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 oznacza post gwiazdką/usuwa gwiazdkę, oraz zwiększoną liczbę gwiazdek. Jeśli już wiemy, że użytkownik jest gwiazdą posta, zamiast transakcji możemy użyć atomowej operacji inkrementacji.

Szybki

Uwaga: Ten produkt nie jest Firebase dostępny na klipie docelowej 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 nie jest Firebase dostępny na klipie docelowej 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 używa operacji transakcji, więc nie jest automatycznie uruchamiany ponownie w przypadku konfliktu aktualizacji. Jednak ponieważ operacja inkrementacji odbywa się bezpośrednio na serwerze bazy danych, nie ma szans na konflikt.

Jeśli chcesz wykrywać i odrzucać konflikty specyficzne dla aplikacji, takie jak użytkownik oznaczony gwiazdką posta, który już wcześniej oznaczył, powinieneś napisać niestandardowe reguły zabezpieczeń 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 połączony z bazą danych Firebase zachowuje własną wewnętrzną wersję wszystkich aktywnych danych. Kiedy dane są zapisywane, najpierw są zapisywane w tej lokalnej wersji. Klient Firebase synchronizuje następnie te dane ze zdalnymi serwerami baz danych oraz z innymi klientami na zasadzie „najlepszych starań”.

W rezultacie wszystkie zapisy w bazie danych natychmiast wyzwalają lokalne zdarzenia, 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 łączności aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania kodu niestandardowego.

Porozmawiamy więcej o zachowanie w trybie offline Dowiedz się więcej na temat możliwości online i offline .

Następne kroki