Lesen und Schreiben von Daten auf iOS

(Optional) Prototyp und Test mit Firebase Local Emulator Suite

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in sie schreibt, stellen wir eine Reihe von Tools vor, die Sie zum Prototyping und Testen der Echtzeitdatenbankfunktionalität verwenden 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 zu arbeiten, ohne Live-Dienste bereitzustellen.

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

Die Verwendung des Echtzeit-Datenbank-Emulators umfasst nur wenige Schritte:

  1. Hinzufügen einer Codezeile zur Testkonfiguration Ihrer App, um eine Verbindung zum Emulator herzustellen.
  2. Von der Wurzel Ihrer lokalen Projektverzeichnis, laufen firebase emulators:start .
  3. Aufrufe über den Prototypcode Ihrer App wie gewohnt mit einem Realtime Database Platform SDK oder mit der Realtime Database REST API.

Eine detaillierte Komplettlösung beteiligt Realtime - Datenbank und Cloud - Funktionen zur Verfügung. Sie sollten auch einen Blick auf die haben Local Emulator Suite Einführung .

Holen Sie sich eine FIRDatenbank-Referenz

Zum Lesen oder Schreiben von Daten aus der Datenbank, müssen 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.

Feuerbasis Daten werden auf eine schriftlichen FIRDatabase Referenz und abgerufen durch einen asynchronen Zuhörer auf die Referenz befestigen. Der Listener wird einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern, getriggert.

Grundlegende Schreibvorgänge

Für grundlegende Schreiboperationen, können Sie setValue Daten zu einer bestimmten Referenz zu speichern, alle vorhandenen Daten auf diesem Weg zu ersetzen. Sie können diese Methode verwenden, um:

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

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

Schnell

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

Ziel c

[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

Mit setValue auf diese Weise überschreibt Daten an der angegebenen Position, 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

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 lauschen

Um Daten auf einem Pfad zu lesen und für Änderungen hören, verwenden Sie die observeEventType:withBlock von FIRDatabaseReference zu beobachten FIRDataEventTypeValue Ereignisse.

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

Sie können die Verwendung FIRDataEventTypeValue Ereignisses , um die Daten zu einem bestimmten Pfad zu lesen, da sie zum Zeitpunkt des Ereignisses vorhanden ist . Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und jedes Mal, wenn sich die Daten, einschließlich der untergeordneten Elemente, ändern. Die Veranstaltung Rückruf wird ein übergebenes snapshot alle Daten an dieser Stelle enthalten, einschließlich der Kinder Daten. Wenn keine Daten vorhanden sind, wird der Snapshot zurückkehren false , wenn Sie anrufen exists() und nil , wenn Sie seine lesen value Eigenschaft.

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
  // ...
})

Ziel c

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

Der Hörer erhält eine FIRDataSnapshot , die die Daten an der angegebenen Stelle in der Datenbank in seiner zum Zeitpunkt des Ereignisses enthält value Eigenschaft. Sie können die Werte in den entsprechenden nativen Typ, wie zuweisen NSDictionary . Wenn keine Daten an der Stelle vorhanden ist , der value ist nil .

Daten einmal lesen

Einmal lesen mit getData()

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 Wertereignistechniken verwenden, um Daten zu lesen, um über Aktualisierungen der Daten vom Back-End 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 verwenden getData() eine Momentaufnahme der Daten aus der Datenbank zu erhalten. Wenn aus irgendeinem Grund getData() nicht in der Lage ist , den Server - Wert zurückzukehren, wird der Client den lokalen Speicher - Cache - Sonde und einen Fehler zurück , wenn der Wert noch nicht gefunden wird.

Unnötige Verwendung von getData() , kann die Verwendung von Bandbreite erhöhen und zu einem Verlust an Leistung führen, die unter Verwendung einer Echtzeit - Listener verhindert werden kann , wie oben gezeigt.

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

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 auf dem Server nach einem aktualisierten Wert zu suchen. In diesen Fällen können Sie verwenden observeSingleEventOfType die Daten sofort aus dem lokalen Festplatten - Cache zu erhalten.

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich nicht häufig ändern oder eine aktive Überwachung erfordern. Die Blogging-App in den vorherigen Beispielen verwendet beispielsweise diese Methode, um das Profil eines Benutzers zu laden, wenn er 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

Spezifische Felder aktualisieren

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

Beim Aufruf updateChildValues können Sie auf niedrigerer Ebene Kind Werte aktualisieren , indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten an mehreren Orten gespeichert ist , besser zu skalieren, können Sie alle Instanzen dieser Daten aktualisieren , die Daten Fanout . Beispielsweise möchte eine Social-Blogging-App möglicherweise einen Beitrag erstellen und ihn gleichzeitig auf den aktuellen Aktivitätsfeed und den Aktivitätsfeed des postenden Benutzers aktualisieren. Dazu verwendet die Blogging-Anwendung Code wie diesen:

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

Mit Hilfe dieser Pfade können Sie gleichzeitige Aktualisierungen auf mehrere Standorte im JSON - Struktur mit einem einzigen Aufruf auszuführen updateChildValues , wie , wie dieses Beispiel in den beiden Standorten den neuen Beitrag erstellt. Gleichzeitige Aktualisierungen, die auf diese Weise durchgeführt werden, sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Einen Abschlussblock hinzufügen

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Vervollständigungsblock hinzufügen. Sowohl setValue und updateChildValues nehmen einen optionalen Abschluss - Block, der aufgerufen wird , wenn der Schreib wird in die Datenbank übernommen worden. 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

Die einfachste Art und Weise zu löschen Daten aufrufen removeValue auf einem Verweis auf den Speicherort der Daten.

Sie können auch durch Angabe löschen nil als Wert für eine weitere Schreiboperation wie setValue oder updateChildValues . Sie können diese Technik mit verwenden updateChildValues , um mehrere Kinder in einem einzigen API - Aufruf zu löschen.

Hörer trennen

Beobachter nicht stoppen Daten automatisch synchronisieren , wenn Sie einen verlassen ViewController . Wenn ein Beobachter nicht ordnungsgemäß entfernt wird, synchronisiert er weiterhin Daten mit dem lokalen Speicher. Wenn ein Beobachter nicht mehr benötigt wird , entfernen , indem es das zugeordnete vorbei FIRDatabaseHandle zur removeObserverWithHandle Methode.

Wenn Sie einen Rückruf Block zu einer Referenz hinzufügen, ein FIRDatabaseHandle zurückgegeben. Diese Handles können verwendet werden, um den Callback-Block zu entfernen.

Wenn einer Datenbankreferenz mehrere Listener hinzugefügt wurden, wird jeder Listener aufgerufen, wenn ein Ereignis ausgelöst wird. Um an dieser Stelle der Synchronisierung von Daten zu stoppen, müssen Sie alle Beobachter an einem Ort entfernen , indem das Aufruf removeAllObservers Methode.

Der Aufruf removeObserverWithHandle oder removeAllObservers auf einem Zuhörer nicht automatisch Hörer entfernen auf seinen untergeordneten Knoten registriert ist ; Sie müssen auch diese Referenzen oder Handles verfolgen, um sie zu entfernen.

Daten als Transaktionen speichern

Bei der Arbeit mit Daten , die durch die gleichzeitigen Änderungen beschädigt werden könnten, wie Inkrementalzähler, können Sie einen verwenden Transaktion Betrieb . Sie geben dieser Operation zwei Argumente: eine Update-Funktion und einen optionalen Abschluss-Callback. 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-App für soziales Bloggen könnten Sie beispielsweise Benutzern erlauben, Beiträge zu markieren und die Markierung aufzuheben und wie folgt zu verfolgen, wie viele Sterne ein Beitrag 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: [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);
  }
}];

Die Verwendung einer Transaktion verhindert, dass die Sternzählung falsch ist, wenn mehrere Benutzer denselben Beitrag gleichzeitig markieren oder der Kunde veraltete Daten hat. Der Wert in der enthaltenen FIRMutableData Klasse ist zunächst der letzte bekannte Wert für den Pfad des Kunden, oder nil , wenn es keinen gibt. 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 mit dem aktualisierten Wert erneut 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/deaktiviert, und die inkrementierte Sternzahl. 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, daher wird er nicht automatisch erneut ausgeführt, wenn ein Update widersprüchlich ist. Da die Inkrementierungsoperation jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Möglichkeit eines Konflikts.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. wenn ein Benutzer einen Beitrag markiert, 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 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 dann diese Daten mit den Remote-Datenbankservern und mit anderen Clients nach dem „Best-Effort“.

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

Sobald die Konnektivität wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne benutzerdefinierten Code schreiben zu müssen.

Wir werden mehr über Offline - Verhalten sprechen in Erfahren Sie mehr über Online - und Offline - Funktionen .

Nächste Schritte