Lesen und Schreiben von Daten auf Apple-Plattformen

(Optional) Prototypen erstellen und mit der Firebase Local Emulator Suite testen

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in sie schreibt, stellen wir eine Reihe von Tools vor, mit denen Sie die Funktionalität der Echtzeitdatenbank prototypisieren und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder die kostengünstigste Möglichkeit für die Interaktion mit dem Backend finden, kann die Möglichkeit, lokal zu arbeiten, ohne Live-Dienste bereitzustellen, eine gute Idee sein.

Ein Echtzeitdatenbank-Emulator ist Teil der Local Emulator Suite, der es Ihrer App ermöglicht, mit Ihrem emulierten Datenbankinhalt und Ihrer emulierten Datenbankkonfiguration sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) zu interagieren.

Die Verwendung des Echtzeitdatenbank-Emulators erfordert nur wenige Schritte:

  1. Fügen Sie der Testkonfiguration Ihrer App eine Codezeile hinzu, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start .
  3. Aufrufe aus dem Prototypcode Ihrer App wie gewohnt mit einem Realtime Database-Plattform-SDK oder mit der Realtime Database REST API durchführen.

Eine detaillierte Anleitung zu Echtzeitdatenbank- und Cloud-Funktionen ist verfügbar. Sie sollten sich auch die Einführung zur Local Emulator Suite ansehen.

Rufen Sie eine FIRDatabaseReference ab

Um Daten aus der Datenbank zu lesen oder zu schreiben, benötigen Sie eine Instanz von FIRDatabaseReference :

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
var ref: DatabaseReference!

ref = Database.database().reference()

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Daten schreiben

Dieses Dokument behandelt die Grundlagen des Lesens und Schreibens von Firebase-Daten.

Firebase-Daten werden in eine Database geschrieben und durch Anhängen eines asynchronen Listeners an die Referenz abgerufen. Der Listener wird einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern, erneut ausgelöst.

Grundlegende Schreiboperationen

Für grundlegende Schreibvorgänge können Sie setValue verwenden, um Daten in einer angegebenen Referenz zu speichern und alle vorhandenen Daten in diesem Pfad zu ersetzen. Mit dieser Methode können Sie Folgendes tun:

  • Übergeben Sie Typen, die den verfügbaren JSON-Typen entsprechen, wie folgt:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Sie können beispielsweise einen Benutzer mit setValue wie folgt hinzufügen:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
self.ref.child("users").child(user.uid).setValue(["username": username])

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

Wenn Sie setValue auf diese Weise verwenden, werden Daten am angegebenen Speicherort überschrieben, einschließlich aller untergeordneten Knoten. Sie können jedoch weiterhin ein untergeordnetes Objekt aktualisieren, ohne das gesamte Objekt neu zu schreiben. Wenn Sie Benutzern erlauben möchten, ihre Profile zu aktualisieren, können Sie den Benutzernamen wie folgt aktualisieren:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
self.ref.child("users/\(user.uid)/username").setValue(username)

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Daten lesen

Lesen Sie Daten, indem Sie auf Wertereignisse warten

Um Daten an einem Pfad zu lesen und auf Änderungen zu warten, verwenden Sie observeEventType:withBlock von FIRDatabaseReference , um FIRDataEventTypeValue Ereignisse zu beobachten.

Ereignistyp Typische Verwendung
FIRDataEventTypeValue Lesen und achten Sie auf Änderungen am gesamten Inhalt eines Pfads.

Sie können das FIRDataEventTypeValue Ereignis verwenden, um die Daten in einem bestimmten Pfad so zu lesen, wie sie zum Zeitpunkt des Ereignisses vorhanden sind. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und jedes Mal erneut, wenn sich die Daten, einschließlich aller untergeordneten Elemente, ändern. Dem Ereignisrückruf wird ein snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, gibt der Snapshot false zurück, wenn Sie exists() aufrufen, und „ nil , wenn Sie die Eigenschaft value lesen.

Das folgende Beispiel zeigt eine Social-Blogging-Anwendung, die die Details eines Beitrags aus der Datenbank abruft:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Der Listener empfängt einen FIRDataSnapshot , der die Daten an der angegebenen Position in der Datenbank zum Zeitpunkt des Ereignisses in seiner value enthält. Sie können die Werte dem entsprechenden nativen Typ zuweisen, z. B. NSDictionary . Wenn am Standort keine Daten vorhanden sind, ist der value nil .

Daten einmal lesen

Einmal lesen mit getData()

Das SDK ist für die Verwaltung von Interaktionen mit Datenbankservern konzipiert, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die oben beschriebenen Wertereignistechniken verwenden, um Daten zu lesen und über Aktualisierungen der Daten vom Backend benachrichtigt zu werden. Diese Techniken reduzieren Ihren Verbrauch und Ihre Kosten und sind darauf optimiert, Ihren Benutzern das beste Erlebnis zu bieten, wenn sie online und offline gehen.

Wenn Sie die Daten nur einmal benötigen, können Sie mit getData() einen Snapshot der Daten aus der Datenbank abrufen. Wenn getData() aus irgendeinem Grund den Serverwert nicht zurückgeben kann, prüft der Client den lokalen Speichercache und gibt eine Fehlermeldung zurück, wenn der Wert immer noch nicht gefunden wird.

Das folgende Beispiel zeigt das einmalige Abrufen des öffentlich zugänglichen Benutzernamens eines Benutzers aus der Datenbank:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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;
}];

Die unnötige Verwendung von getData() kann die Nutzung der Bandbreite erhöhen und zu Leistungseinbußen führen, was durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden kann.

Lesen Sie die Daten einmal mit einem Beobachter

In einigen Fällen möchten Sie möglicherweise, dass der Wert aus dem lokalen Cache sofort zurückgegeben wird, anstatt auf dem Server nach einem aktualisierten Wert zu suchen. In diesen Fällen können Sie observeSingleEventOfType verwenden, um die Daten sofort aus dem lokalen Festplatten-Cache abzurufen.

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder eine aktive Überwachung erfordern. Beispielsweise verwendet die Blogging-App in den vorherigen Beispielen diese Methode, um das Profil eines Benutzers zu laden, wenn dieser mit dem Verfassen eines neuen Beitrags beginnt:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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)
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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);
}];

Daten aktualisieren oder löschen

Aktualisieren Sie bestimmte Felder

Um gleichzeitig auf bestimmte untergeordnete Knoten eines Knotens zu schreiben, ohne andere untergeordnete Knoten zu überschreiben, verwenden Sie die Methode updateChildValues .

Wenn Sie updateChildValues ​​aufrufen, können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten zur besseren Skalierung an mehreren Standorten gespeichert werden, können Sie alle Instanzen dieser Daten mithilfe von Daten-Fanout aktualisieren. Beispielsweise möchte eine Social-Blogging-App möglicherweise einen Beitrag erstellen und ihn gleichzeitig auf den Feed der letzten Aktivitäten und den Aktivitäts-Feed des postenden Benutzers aktualisieren. Zu diesem Zweck verwendet die Blogging-Anwendung Code wie diesen:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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)

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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];

In diesem Beispiel wird childByAutoId verwendet, um einen Beitrag im Knoten zu erstellen, der Beiträge für alle Benutzer unter /posts/$postid enthält, und gleichzeitig den Schlüssel mit getKey() abzurufen. Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Beiträgen des Benutzers unter /user-posts/$userid/$postid zu erstellen.

Mit diesen Pfaden können Sie mit einem einzigen Aufruf von updateChildValues ​​gleichzeitig Aktualisierungen an mehreren Stellen im JSON-Baum durchführen, wie in diesem Beispiel beispielsweise der neue Beitrag an beiden Stellen erstellt wird. Auf diese Weise durchgeführte gleichzeitige Aktualisierungen sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Fügen Sie einen Abschlussblock hinzu

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschlussblock hinzufügen. Sowohl setValue als auch updateChildValues ​​benötigen einen optionalen Abschlussblock, der aufgerufen wird, wenn der Schreibvorgang in der Datenbank festgeschrieben wurde. Dieser Listener kann nützlich sein, um zu verfolgen, welche Daten gespeichert wurden und welche Daten noch synchronisiert werden. Wenn der Aufruf nicht erfolgreich war, wird dem Listener ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
do {
  try await ref.child("users").child(user.uid).setValue(["username": username])
  print("Data saved successfully!")
} catch {
  print("Data could not be saved: \(error).")
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[[[_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.");
  }
}];

Daten löschen

Der einfachste Weg, Daten zu löschen, besteht darin, removeValue für einen Verweis auf den Speicherort dieser Daten aufzurufen.

Sie können auch löschen, indem Sie nil als Wert für einen anderen Schreibvorgang angeben, z. B. setValue oder updateChildValues ​​. Sie können diese Technik mit updateChildValues ​​verwenden, um mehrere untergeordnete Elemente in einem einzigen API-Aufruf zu löschen.

Trennen Sie Zuhörer ab

Beobachter stoppen die Datensynchronisierung nicht automatisch, wenn Sie einen ViewController verlassen. Wenn ein Beobachter nicht ordnungsgemäß entfernt wird, synchronisiert er weiterhin Daten mit dem lokalen Speicher. Wenn ein Beobachter nicht mehr benötigt wird, entfernen Sie ihn, indem Sie das zugehörige FIRDatabaseHandle an die Methode removeObserverWithHandle übergeben.

Wenn Sie einer Referenz einen Rückrufblock hinzufügen, wird ein FIRDatabaseHandle zurückgegeben. Mit diesen Handles kann der Callback-Block entfernt werden.

Wenn einer Datenbankreferenz mehrere Listener hinzugefügt wurden, wird jeder Listener aufgerufen, wenn ein Ereignis ausgelöst wird. Um die Synchronisierung von Daten an diesem Standort zu beenden, müssen Sie alle Beobachter an einem Standort entfernen, indem Sie die Methode removeAllObservers aufrufen.

Durch den Aufruf von removeObserverWithHandle oder removeAllObservers für einen Listener werden die auf seinen untergeordneten Knoten registrierten Listener nicht automatisch entfernt. Sie müssen diese Verweise oder Handles auch im Auge behalten, um sie zu entfernen.

Daten als Transaktionen speichern

Wenn Sie mit Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, können Sie eine Transaktionsoperation verwenden. Sie geben dieser Operation zwei Argumente: eine Aktualisierungsfunktion und einen optionalen Abschlussrückruf. Die Update-Funktion nimmt den aktuellen Zustand der Daten als Argument und gibt den neuen gewünschten Zustand zurück, den Sie schreiben möchten.

In der Beispiel-Social-Blogging-App könnten Sie Benutzern beispielsweise erlauben, Beiträge zu markieren bzw. die Markierung aufzuheben und zu verfolgen, wie viele Sterne ein Beitrag wie folgt erhalten hat:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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)
  }
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[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);
  }
}];

Mithilfe einer Transaktion wird verhindert, dass die Anzahl der Sterne falsch ist, wenn mehrere Benutzer gleichzeitig denselben Beitrag markieren oder der Kunde über veraltete Daten verfügt. Der in der FIRMutableData Klasse enthaltene Wert ist zunächst der letzte bekannte Wert des Clients für den Pfad oder nil , wenn keiner vorhanden ist. Der Server vergleicht den Anfangswert mit seinem aktuellen Wert und akzeptiert die Transaktion, wenn die Werte übereinstimmen, oder lehnt sie ab. Wenn die Transaktion abgelehnt wird, gibt der Server den aktuellen Wert an den Client zurück, der die Transaktion erneut mit dem aktualisierten Wert ausführt. Dies wiederholt sich, bis die Transaktion akzeptiert wird oder zu viele Versuche unternommen wurden.

Atomare serverseitige Inkremente

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Benutzers, der den Beitrag markiert bzw. die Markierung aufhebt, und die erhöhte Anzahl der Sterne. Wenn wir bereits wissen, dass der Benutzer den Beitrag markiert, können wir anstelle einer Transaktion eine atomare Inkrementierungsoperation verwenden.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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)

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
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];

Dieser Code verwendet keinen Transaktionsvorgang und wird daher nicht automatisch erneut ausgeführt, wenn es zu einem Konflikt mit der Aktualisierung kommt. Da der Inkrementierungsvorgang jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Möglichkeit eines Konflikts.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, beispielsweise wenn ein Benutzer einen Beitrag markiert, den er bereits zuvor markiert hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.

Arbeiten Sie offline mit Daten

Wenn ein Client seine Netzwerkverbindung verliert, funktioniert Ihre App weiterhin ordnungsgemäß.

Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet seine eigene interne Version aller aktiven Daten. Wenn Daten geschrieben werden, werden sie zuerst in diese lokale Version geschrieben. Der Firebase-Client synchronisiert diese Daten dann nach dem „Best-Effort“-Prinzip mit den Remote-Datenbankservern und mit anderen Clients.

Dadurch lösen alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse aus, bevor Daten auf den Server geschrieben werden. Dies bedeutet, dass Ihre App unabhängig von Netzwerklatenz oder Konnektivität reaktionsfähig bleibt.

Sobald die Verbindung wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne dass benutzerdefinierter Code geschrieben werden muss.

Weitere Informationen zum Offline-Verhalten finden Sie unter Weitere Informationen zu Online- und Offline-Funktionen .

Nächste Schritte