Daten auf Apple-Plattformen lesen und schreiben

Optional: Prototyp erstellen und mit Firebase Local Emulator Suite testen

Bevor wir uns damit befassen, wie Ihre App Daten aus Realtime Database liest und in Realtime Database schreibt, stellen wir Ihnen einige Tools vor, mit denen Sie Prototypen erstellen und die Realtime Database-Funktionen testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder die kostengünstigste Möglichkeit zur Interaktion mit dem Back-End finden möchten, kann es sehr hilfreich sein, lokal arbeiten zu können, ohne Live-Dienste bereitzustellen.

Ein Realtime Database-Emulator ist Teil des Local Emulator Suite. Er ermöglicht es Ihrer App, mit den emulierten Datenbankinhalten und der emulierten Konfiguration sowie optional mit den emulierten Projektressourcen (Funktionen, anderen Datenbanken und Sicherheitsregeln) zu interagieren.

Die Verwendung des Realtime Database-Emulators ist ganz einfach:

  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 aus.
  3. Sie können wie gewohnt über ein Realtime Database-Plattform-SDK oder die Realtime Database REST API Aufrufe aus dem Prototypcode Ihrer App starten.

Eine detaillierte Anleitung für Realtime Database und Cloud Functions ist verfügbar. Sehen Sie sich auch die Local Emulator SuiteEinführung an.

FIRDatabaseReference abrufen

Wenn Sie Daten aus der Datenbank lesen oder schreiben möchten, benötigen Sie eine Instanz von FIRDatabaseReference:

Swift

Hinweis:Dieses Firebase-Produkt ist für das App-Clip-Ziel nicht verfügbar.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

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

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

Daten schreiben

In diesem Dokument werden die Grundlagen zum Lesen und Schreiben von Firebase-Daten erläutert.

Firebase-Daten werden in eine Database-Referenz geschrieben und abgerufen, indem ein asynchroner Listener an die Referenz angehängt wird. Der Listener wird einmal für den anfänglichen Status der Daten und dann bei jeder Änderung der Daten ausgelöst.

Grundlegende Schreibvorgänge

Bei einfachen Schreibvorgängen können Sie mit setValue Daten in einer bestimmten Referenz speichern und alle vorhandenen Daten an diesem Pfad ersetzen. Mit dieser Methode können Sie Folgendes tun:

  • Karten-/Tickettypen, die den verfügbaren JSON-Typen entsprechen:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

So fügen Sie beispielsweise einen Nutzer mit setValue hinzu:

Swift

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

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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 die Daten an diesem Speicherort überschrieben, einschließlich aller untergeordneten Knoten. Sie können ein untergeordnetes Objekt jedoch aktualisieren, ohne das gesamte Objekt neu zu schreiben. Wenn Sie Nutzern erlauben möchten, ihre Profile zu aktualisieren, können Sie den Nutzernamen so aktualisieren:

Swift

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

Objective-C

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

Daten lesen

Daten lesen, indem auf Wert-Ereignisse gewartet wird

Wenn Sie Daten an einem Pfad lesen und auf Änderungen achten möchten, verwenden Sie die observeEventType:withBlock von FIRDatabaseReference, um FIRDataEventTypeValue-Ereignisse zu beobachten.

Ereignistyp Typische Verwendung
FIRDataEventTypeValue Änderungen am gesamten Inhalt eines Pfads lesen und beobachten.

Mit dem Ereignis FIRDataEventTypeValue können Sie die Daten an einem bestimmten Pfad lesen, wie sie zum Zeitpunkt des Ereignisses vorhanden sind. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und dann jedes Mal, wenn sich die Daten, einschließlich aller untergeordneten Elemente, ändern. Dem Ereignis-Callback wird eine snapshot übergeben, die 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 Property value lesen.

Im folgenden Beispiel wird eine Social-Blogging-Anwendung veranschaulicht, die die Details eines Beitrags aus der Datenbank abruft:

Swift

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

Objective-C

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

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

Daten einmal lesen

Einmal mit getData() lesen

Das SDK wurde entwickelt, um Interaktionen mit Datenbankservern zu verwalten, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die oben beschriebenen Methoden für Wert-Ereignisse verwenden, um Daten zu lesen und über Aktualisierungen der Daten aus dem Backend benachrichtigt zu werden. Diese Techniken reduzieren die Nutzung und Abrechnung und sind so optimiert, dass Nutzer beim Wechsel zwischen Online- und Offlinemodus die bestmögliche Erfahrung haben.

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, sucht der Client im Cache des lokalen Speichers und gibt einen Fehler zurück, wenn der Wert immer noch nicht gefunden wird.

Im folgenden Beispiel wird gezeigt, wie der öffentliche Nutzername eines Nutzers einmal aus der Datenbank abgerufen wird:

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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)
}

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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 Bandbreitennutzung erhöhen und zu Leistungseinbußen führen. Dies kann durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden.

Daten einmal mit einem Beobachter lesen

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

Das ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder für die aktives Zuhören erforderlich ist. In der Blogging-App aus den vorherigen Beispielen wird diese Methode beispielsweise verwendet, um das Profil eines Nutzers zu laden, wenn er mit dem Verfassen eines neuen Beitrags beginnt:

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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)
}

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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

Bestimmte Felder aktualisieren

Wenn Sie gleichzeitig in bestimmte untergeordnete Elemente eines Knotens schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, verwenden Sie die Methode updateChildValues.

Wenn Sie updateChildValues aufrufen, können Sie untergeordnete Werte der unteren Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten zur besseren Skalierung an mehreren Orten gespeichert werden, können Sie alle Instanzen dieser Daten mithilfe der Datenfan-out-Funktion aktualisieren. Beispiel: In einer Social-Blogging-App kann ein Beitrag erstellt und gleichzeitig im Feed der letzten Aktivitäten und im Feed der Aktivitäten des Nutzers aktualisiert werden. Dazu verwendet die Blogging-Anwendung Code wie diesen:

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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)

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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 mit childByAutoId ein Beitrag im Knoten erstellt, der Beiträge für alle Nutzer unter /posts/$postid enthält. Gleichzeitig wird der Schlüssel mit getKey() abgerufen. Mit dem Schlüssel kann dann ein zweiter Eintrag in den Beiträgen des Nutzers unter /user-posts/$userid/$postid erstellt werden.

Mit diesen Pfaden kannst du mit einem einzigen Aufruf von updateChildValues gleichzeitig mehrere Stellen im JSON-Baum aktualisieren. In diesem Beispiel wird beispielsweise der neue Beitrag an beiden Stellen erstellt. Simultane Aktualisierungen auf diese Weise sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle schlagen fehl.

Abschlussblock hinzufügen

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

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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).")
}

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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

Am einfachsten lassen sich Daten löschen, indem Sie removeValue auf eine Referenz zum Speicherort dieser Daten anwenden.

Sie können auch löschen, indem Sie nil als Wert für einen anderen Schreibvorgang wie setValue oder updateChildValues angeben. Mit dieser Methode und updateChildValues können Sie mehrere untergeordnete Elemente in einem einzigen API-Aufruf löschen.

Listener trennen

Die Datensynchronisierung für Beobachter wird nicht automatisch beendet, wenn Sie eine ViewController verlassen. Wenn ein Beobachter nicht richtig entfernt wird, werden weiterhin Daten mit dem lokalen Speicher synchronisiert. Wenn ein Beobachter nicht mehr benötigt wird, entfernen Sie ihn, indem Sie die zugehörige FIRDatabaseHandle an die removeObserverWithHandle-Methode übergeben.

Wenn Sie einer Referenz einen Callback-Block hinzufügen, wird FIRDatabaseHandle zurückgegeben. Mit diesen Handles kannst du den Callback-Block entfernen.

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

Wenn Sie removeObserverWithHandle oder removeAllObservers auf einen Listener anwenden, werden die bei den 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önnen, z. B. mit inkrementellen Zählern, können Sie einen Transaktionsvorgang verwenden. Sie geben diesem Vorgang zwei Argumente: eine Aktualisierungsfunktion und einen optionalen Rückruf bei Abschluss. Die Update-Funktion nimmt den aktuellen Status der Daten als Argument und gibt den neuen gewünschten Status zurück, den Sie schreiben möchten.

In der Beispiel-App für soziales Bloggen könnten Sie Nutzern beispielsweise erlauben, Beiträge zu bewerten und die Anzahl der Sterne zu verfolgen, die ein Beitrag erhalten hat. Dazu gehen Sie so vor:

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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)
  }
}

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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);
  }
}];

Durch die Verwendung einer Transaktion wird verhindert, dass die Anzahl der Sterne falsch ist, wenn mehrere Nutzer gleichzeitig denselben Beitrag mit einem Stern markieren oder der Client veraltete Daten hat. Der in der FIRMutableData-Klasse enthaltene Wert ist anfangs der letzte bekannte Wert des Clients für den Pfad oder nil, falls keiner vorhanden ist. Der Server vergleicht den ursprünglichen Wert mit dem 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 dann noch einmal mit dem aktualisierten Wert ausführt. Dieser Vorgang wird wiederholt, bis die Transaktion akzeptiert wird oder zu viele Versuche unternommen wurden.

Atomare serverseitige Increments

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Nutzers, der dem Beitrag ein bzw. kein Sternchen gibt, und die inkrementierte Anzahl der Sterne. Wenn wir bereits wissen, dass der Nutzer den Beitrag mit einem Stern markiert, können wir anstelle einer Transaktion einen atomaren Inkrementierungsvorgang verwenden.

Swift

Hinweis:Dieses Firebase-Produkt ist für das 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)

Objective-C

Hinweis:Dieses Firebase-Produkt ist für das 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];

Da in diesem Code kein Transaktionsvorgang verwendet wird, wird er nicht automatisch neu ausgeführt, wenn es ein in Konflikt stehendes Update gibt. Da der Inkrementenvorgang jedoch direkt auf dem Datenbankserver ausgeführt wird, besteht keine Gefahr eines Konflikts.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. wenn ein Nutzer einem Beitrag ein Sternchen hinzufügt, das er ihm bereits zuvor gegeben hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.

Offline mit Daten arbeiten

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

Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet seine eigenen internen Versionen aller aktiven Daten. Beim Schreiben von Daten werden sie zuerst in diese lokale Version geschrieben. Der Firebase-Client synchronisiert diese Daten dann auf Best-Effort-Basis mit den Remote-Datenbankservern und anderen Clients.

Daher werden alle Schreibvorgänge in der Datenbank sofort als lokale Ereignisse ausgelöst, bevor Daten auf den Server geschrieben werden. Das bedeutet, dass Ihre App unabhängig von der Netzwerklatenz oder der Verbindung reaktionsschnell bleibt.

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

Weitere Informationen zum Offlineverhalten finden Sie unter Weitere Informationen zu Online- und Offlinefunktionen.

Nächste Schritte