Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Lesen und Schreiben von Daten unter iOS

(Optional) Prototyp und Test mit Firebase Local Emulator Suite

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in diese schreibt, stellen wir Ihnen eine Reihe von Tools vor, mit denen Sie die Funktionen der Echtzeitdatenbank prototypisieren und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder nach der kostengünstigsten Möglichkeit suchen, mit dem Back-End zu interagieren, kann es eine gute Idee sein, lokal ohne Bereitstellung von Live-Diensten arbeiten zu können.

Ein Echtzeit-Datenbankemulator ist Teil der Local Emulator Suite, mit der Ihre App mit Ihrem emulierten Datenbankinhalt und Ihrer Konfiguration sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) interagieren kann. Beachten Sie, dass die Local Emulator Suite emulierten Cloud-Speicher noch nicht unterstützt.

Die Verwendung des Echtzeitdatenbank-Emulators umfasst nur wenige Schritte:

  1. Hinzufügen einer Codezeile zur Testkonfiguration Ihrer App, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start .
  3. Anrufe aus dem Prototypcode Ihrer App wie gewohnt mit einem SDK der Echtzeitdatenbankplattform oder mithilfe der REST-API der Echtzeitdatenbank tätigen.

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

Holen Sie sich eine FIRDatabaseReference

Zum Lesen oder Schreiben von Daten aus der Datenbank benötigen Sie eine Instanz von FIRDatabaseReference :

Schnell

var ref: DatabaseReference!

ref = Database.database().reference()

Ziel c

@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Daten schreiben

Dieses Dokument behandelt die Grundlagen zum Lesen und Schreiben von Firebase-Daten.

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

Grundlegende Schreibvorgänge

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

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

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

Schnell

self.ref.child("users").child(user.uid).setValue(["username": username])

Ziel c

[[[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 setValue 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

self.ref.child("users/\(user.uid)/username").setValue(username)

Ziel c

[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Daten lesen

Lesen Sie Daten, indem Sie auf Wertereignisse warten

Verwenden Sie den observeEventType:withBlock von FIRDatabaseReference , um Daten in einem Pfad zu lesen und auf Änderungen zu FIRDatabaseReference , um FIRDataEventTypeValue Ereignisse zu beobachten.

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

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

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

Schnell

refHandle = postRef.observe(DataEventType.value, with: { (snapshot) in
  let postDict = snapshot.value as? [String : AnyObject] ?? [:]
  // ...
})

Ziel c

_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Der Listener empfängt einen FIRDataSnapshot , der die Daten zum angegebenen Zeitpunkt in der value Eigenschaft am angegebenen Speicherort in der Datenbank 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 mit getData () lesen

Das SDK dient zur Verwaltung von Interaktionen mit Datenbankservern, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die oben beschriebenen Werteereignis-Techniken verwenden, um Daten zu lesen und über Aktualisierungen der Daten vom Backend benachrichtigt zu werden. Diese Techniken reduzieren Ihre Nutzung und Abrechnung und sind optimiert, um Ihren Benutzern die beste Erfahrung 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 nicht zurückgeben kann, getData() der Client den lokalen Speichercache und gibt einen Fehler zurück, wenn der Wert immer noch nicht gefunden wird.

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

self.ref.child("users/\(user.uid)/username").getData { (error, snapshot) in
    if let error = error {
        print("Error getting data \(error)")
    }
    else if snapshot.exists() {
        print("Got data \(snapshot.value!)")
    }
    else {
        print("No data available")
    }
}

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 , um die Daten sofort aus dem lokalen Festplatten-Cache observeSingleEventOfType .

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder aktives Abhören 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

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

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);
}];

Aktualisieren oder Löschen von Daten

Aktualisieren Sie bestimmte Felder

Verwenden Sie die updateChildValues Methode, um gleichzeitig auf bestimmte updateChildValues Knoten eines Knotens zu schreiben, ohne andere updateChildValues Knoten zu überschreiben.

Wenn Sie updateChildValues aufrufen, können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel updateChildValues . Wenn Daten an mehreren Orten gespeichert werden, um eine bessere Skalierung zu erreichen, können Sie alle Instanzen dieser Daten mithilfe des Daten-Fan-Outs aktualisieren. Beispielsweise möchte eine Social-Blogging-App möglicherweise einen Beitrag erstellen und ihn gleichzeitig auf den letzten Aktivitäts-Feed und den Aktivitäts-Feed des Posting-Benutzers aktualisieren. Zu diesem Zweck verwendet die Blogging-Anwendung folgenden Code:

Schnell

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

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 , um einen Beitrag im Knoten zu erstellen, der Beiträge für alle Benutzer unter /posts/$postid und gleichzeitig den Schlüssel mit getKey() . Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Posts des Benutzers unter /user-posts/$userid/$postid .

Mithilfe dieser Pfade können Sie mit einem einzigen Aufruf von updateChildValues gleichzeitig Aktualisierungen an mehreren Speicherorten im JSON-Baum updateChildValues , z. B. wie in diesem Beispiel der neue Beitrag an beiden Speicherorten erstellt wird. Auf diese Weise vorgenommene 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 einen optionalen Abschlussblock, der aufgerufen wird, wenn der Schreibvorgang in die Datenbank übernommen 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

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!")
  }
}

Ziel c

[[[_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 removeValue .

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

Hörer abnehmen

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

Wenn Sie einer Referenz einen FIRDatabaseHandle hinzufügen, wird ein FIRDatabaseHandle zurückgegeben. Mit diesen Handles kann der Rückrufblock 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 Speicherort zu beenden, müssen Sie alle Beobachter an einem Speicherort entfernen, indem Sie die Methode removeAllObservers aufrufen.

Durch Aufrufen von removeObserverWithHandle oder removeAllObservers in einem Listener werden die auf den removeAllObservers Knoten registrierten Listener nicht automatisch entfernt. Sie müssen auch diese Referenzen oder Handles verfolgen, 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 Rückruf zum Abschluss. Die Aktualisierungsfunktion verwendet 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önnen Sie Benutzern beispielsweise erlauben, Beiträge zu markieren und zu entfernen und zu verfolgen, wie viele Sterne ein Beitrag wie folgt erhalten hat:

Schnell

ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
    var stars: Dictionary<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

[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 Benutzer denselben Beitrag gleichzeitig 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 wenn keiner vorhanden ist. Der Server vergleicht den Anfangswert 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 erneut mit dem aktualisierten Wert ausführt. Dies wird wiederholt, bis die Transaktion akzeptiert wurde oder zu viele Versuche unternommen wurden.

Atomic serverseitige Inkremente

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

Schnell

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

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 keine Transaktionsoperation und wird daher bei einem widersprüchlichen Update nicht automatisch erneut ausgeführt. Da die Inkrementierungsoperation jedoch direkt auf dem Datenbankserver ausgeführt wird, besteht keine Möglichkeit eines Konflikts.

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

Offline mit Daten arbeiten

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

Jeder mit einer Firebase-Datenbank verbundene Client 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 mit den Remote-Datenbankservern und mit anderen Clients auf "Best-Effort" -Basis.

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

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