Odczytywanie i zapisywanie danych na platformach Apple

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

(Opcjonalnie) Prototypuj i testuj za pomocą 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: Firebase Local Emulator Suite. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły bezpieczeństwa 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ą pakietu Local Emulator Suite, który umożliwia aplikacji interakcję z emulowaną zawartością i konfiguracją bazy danych, a także opcjonalnie 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, uruchamiając firebase emulators:start .
  3. Wykonywanie wywołań z kodu prototypu Twojej aplikacji przy użyciu pakietu SDK platformy Realtime Database w zwykły sposób lub przy użyciu interfejsu API REST bazy danych czasu rzeczywistego.

Dostępny jest szczegółowy przewodnik dotyczący bazy danych czasu rzeczywistego i funkcji chmury . Warto również zapoznać się z wprowadzeniem do pakietu Local Emulator Suite .

Uzyskaj FIRDatabaseReference

Aby odczytywać lub zapisywać 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 przez dołączenie asynchronicznego odbiornika do odwołania. Odbiornik jest uruchamiany raz dla początkowego stanu danych i ponownie za każdym razem, gdy dane się zmieniają.

Podstawowe operacje zapisu

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

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

Na przykład możesz dodać użytkownika z 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 nadpisuje dane w określonej lokalizacji, w tym wszelkie węzły podrzędne. Jednak nadal możesz zaktualizować obiekt podrzędny 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 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];

Odczyt danych

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

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

Typ wydarzenia Typowe użycie
FIRDataEventTypeValue Odczytywanie i nasłuchiwanie zmian w całej zawartości ścieżki.

Możesz użyć zdarzenia FIRDataEventTypeValue , aby odczytać dane w danej ścieżce, ponieważ istnieją one w momencie zdarzenia. Ta metoda jest uruchamiana raz, gdy odbiornik jest podłączony, i ponownie za każdym razem, gdy zmieniają się dane, w tym wszelkie elementy podrzędne. Wywołanie zwrotne zdarzenia przekazuje snapshot zawierającą wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma żadnych danych, migawka zwróci false , gdy wywołasz exists() i nil , gdy odczytasz jej właściwość 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 czasie zdarzenia w jego właściwości value . Możesz przypisać wartości do odpowiedniego typu natywnego, takiego jak NSDictionary . Jeśli w lokalizacji nie ma żadnych danych, value jest nil .

Odczytaj 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, powinieneś używać opisanych powyżej technik zdarzeń wartości, aby odczytywać dane i otrzymywać powiadomienia o aktualizacjach danych z zaplecza. Techniki te ograniczają użycie i rozliczenia oraz są zoptymalizowane, aby zapewnić użytkownikom najlepsze wrażenia podczas korzystania z Internetu i offline.

Jeśli potrzebujesz danych tylko raz, możesz użyć getData() , aby uzyskać migawkę danych z bazy danych. Jeśli z jakiegokolwiek powodu 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 pobieranie publicznej nazwy użytkownika z bazy danych:

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 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 doprowadzić do utraty wydajności, czemu można zapobiec, używając odbiornika działającego w czasie rzeczywistym, jak pokazano powyżej.

Odczytaj 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żesz użyć observeSingleEventOfType , aby natychmiast pobrać dane z lokalnej pamięci podręcznej dysku.

Jest to przydatne w przypadku danych, które muszą zostać załadowane tylko raz i nie oczekuje się, że będą się często zmieniać ani wymagać aktywnego słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do zał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);
}];

Aktualizowanie lub usuwanie danych

Zaktualizuj określone pola

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

Podczas wywoływania metody 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 lokalizacjach w celu lepszego skalowania, możesz zaktualizować wszystkie instancje tych danych, korzystając z funkcji rozkładania danych . Na przykład aplikacja do blogowania społecznościowego może chcieć utworzyć post i jednocześnie zaktualizować go do źródła ostatniej aktywności i źródła aktywności użytkownika publikującego. 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 , aby utworzyć post w węźle zawierającym posty dla wszystkich użytkowników w /posts/$postid i jednocześnie pobrać klucz za pomocą getKey() . Klucz może być następnie użyty do utworzenia drugiego wpisu w postach użytkownika w /user-posts/$userid/$postid .

Korzystając z tych ścieżek, możesz przeprowadzać jednoczesne aktualizacje w wielu lokalizacjach w drzewie JSON za pomocą jednego wywołania updateChildValues ​​, na przykład w ten sposób tworzysz nowy wpis w obu lokalizacjach. Jednoczesne aktualizacje przeprowadzane w ten sposób są niepodzielne: albo wszystkie aktualizacje powiodą się, albo wszystkie aktualizacje się nie powiodą.

Dodaj blok uzupełniający

Jeśli chcesz wiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać blok uzupełniania. Zarówno setValue jak i updateChildValues ​​przyjmują opcjonalny blok uzupełniania, który jest wywoływany, gdy zapis został 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 nie powiodło się, słuchacz otrzymuje obiekt błędu wskazujący przyczynę niepowodzenia.

Szybki

Uwaga: ten produkt Firebase nie jest dostępny w docelowym klipie 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 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żesz także usunąć, określając nil jako wartość dla 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 API.

Odłącz słuchaczy

Obserwatorzy nie przestają automatycznie synchronizować danych po opuszczeniu ViewController . Jeśli obserwator nie zostanie prawidłowo usunięty, kontynuuje synchronizację danych 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 . Te uchwyty mogą służyć do usuwania bloku wywołania zwrotnego.

Jeśli do odwołania do bazy danych dodano wielu detektorów, każdy detektor jest wywoływany, gdy zostanie zgłoszone zdarzenie. Aby zatrzymać synchronizację danych w tej lokalizacji, należy usunąć wszystkich obserwatorów z danej lokalizacji, wywołując metodę removeAllObservers .

Wywołanie metody removeObserverWithHandle lub removeAllObservers na odbiorniku nie powoduje automatycznego usunięcia detektoró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 jako argument bieżący stan danych 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 gwiazdką i usuwaniem gwiazdek oraz śledzić, ile gwiazdek 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);
  }
}];

Korzystanie z transakcji zapobiega nieprawidłowemu liczeniu 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 nie istnieje. Serwer porównuje wartość początkową z jej bieżącą wartością i akceptuje transakcję, jeśli wartości są zgodne, lub ją odrzuca. Jeśli 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 przykładzie użycia zapisujemy w bazie danych dwie wartości: identyfikator użytkownika, który oznaczył/usunął post gwiazdką, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy już, że użytkownik oznacza post gwiazdką, zamiast transakcji możemy użyć atomowej operacji inkrementacji.

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 korzysta z operacji transakcji, więc nie jest automatycznie uruchamiany ponownie, jeśli występuje konflikt aktualizacji. Ponieważ jednak operacja inkrementacji odbywa się bezpośrednio na serwerze bazy danych, nie ma możliwości wystąpienia konfliktu.

Jeśli chcesz wykryć i odrzucić konflikty specyficzne dla aplikacji, takie jak oznaczenie przez użytkownika posta, który już wcześniej oznaczył gwiazdką, należy 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. Gdy dane są zapisywane, są one najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami baz danych i innymi klientami na zasadzie „najlepszych 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 przywróceniu łączności 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 na temat zachowania w trybie offline znajdziesz w artykule Dowiedz się więcej o możliwościach w trybie online i offline .

Następne kroki