Mobile Programmierer nutzen seit vielen Jahren die Mobile Backend as a Service (MBaaS)-Plattform Firebase Realtime Database von Google, die ihnen hilft, sich auf die Entwicklung von Funktionen für ihre Apps zu konzentrieren, ohne sich um die Back-End-Infrastruktur und Datenbank kümmern zu müssen. Indem es das Speichern und Persistieren von Daten in der Cloud vereinfacht und sich um Authentifizierung und Sicherheit kümmert, ermöglicht Firebase Programmierern, sich auf die Client-Seite zu konzentrieren.
Letztes Jahr hat Google mit Cloud Firestore eine weitere Back-End-Datenbanklösung angekündigt, die von Grund auf mit dem Versprechen größerer Skalierbarkeit und Intuitivität entwickelt wurde. Dies führte jedoch zu einiger Verwirrung hinsichtlich seines Platzes in Bezug auf das bereits vorhandene Flaggschiffprodukt von Google, die Firebase Realtime Database. In diesem Tutorial werden die Unterschiede zwischen den beiden Plattformen und die jeweiligen Vorteile beschrieben. Sie lernen, wie Sie mit Firestore-Dokumentreferenzen arbeiten und Daten in Echtzeit lesen, schreiben, aktualisieren und löschen, indem Sie eine einfache Erinnerungs-App erstellen.
Ziele dieses Tutorials
In dieser Anleitung lernen Sie Cloud Firestore kennen. Sie erfahren, wie Sie die Plattform für Datenbankpersistenz und -synchronisierung in Echtzeit nutzen können. Wir behandeln die folgenden Themen:
- was Cloud Firestore ist
- das Firestore-Datenmodell
- Cloud Firestore einrichten
- Erstellen von und Arbeiten mit Cloud Firestore Referenzen
- Lesen von Daten in Echtzeit aus Cloud Firestore
- Erstellen, Aktualisieren und Löschen von Daten
- Filtern und zusammengesetzte Abfragen
Angenommenes Wissen
In diesem Tutorial wird davon ausgegangen, dass Sie Firebase bereits kennengelernt haben und sich mit der Entwicklung von Swift und Xcode auskennen.
Was ist Cloud Firestore?
Wie Firebase Realtime Database bietet Firestore Mobil- und Webentwicklern eine plattformübergreifende Cloud-Lösung, um Daten in Echtzeit zu speichern, unabhängig von Netzwerklatenz oder Internetverbindung, sowie eine nahtlose Integration in die Produktsuite der Google Cloud Platform. Neben diesen Ähnlichkeiten gibt es deutliche Vor- und Nachteile, die sich voneinander unterscheiden.
Datenmodell
Grundsätzlich speichert Realtime Database Daten als einen großen, monolithischen, hierarchischen JSON-Baum, während Firestore Daten in Dokumenten und Sammlungen sowie Untersammlungen organisiert. Dies erfordert weniger Denormalisierung. Das Speichern von Daten in einem JSON-Baum hat den Vorteil der Einfachheit, wenn es um die Arbeit mit einfachen Datenanforderungen geht; Es wird jedoch umständlicher, wenn Sie mit komplexeren hierarchischen Daten arbeiten.
Offline-Support
Beide Produkte bieten Offline-Unterstützung, aktives Zwischenspeichern von Daten in Warteschlangen, wenn eine latente oder keine Netzwerkverbindung besteht, und Synchronisieren lokaler Änderungen zurück zum Backend, wenn möglich. Firestore unterstützt neben mobilen Apps auch die Offline-Synchronisierung für Web-Apps, während die Realtime Database nur die mobile Synchronisierung ermöglicht.
Abfragen und Transaktionen
Realtime Database unterstützt nur eingeschränkte Sortier- und Filterfunktionen – Sie können in einer einzigen Abfrage nur auf Eigenschaftsebene sortieren oder filtern, aber nicht beides. Abfragen sind auch tief, was bedeutet, dass sie einen großen Teilbaum von Ergebnissen zurückgeben. Das Produkt unterstützt nur einfache Schreib- und Transaktionsvorgänge, die einen Abschluss-Callback erfordern.
Firestore hingegen führt Indexabfragen mit zusammengesetzter Sortierung und Filterung ein, sodass Sie Aktionen kombinieren können, um Kettenfilter und Sortierungen zu erstellen. Sie können auch flache Abfragen ausführen, die Untersammlungen anstelle der gesamten Sammlung zurückgeben, die Sie mit Realtime Database erhalten würden. Transaktionen sind von Natur aus atomar, unabhängig davon, ob Sie eine Batch-Operation oder eine einzelne senden, wobei Transaktionen automatisch wiederholt werden, bis sie abgeschlossen sind. Darüber hinaus unterstützt Realtime Database nur einzelne Schreibtransaktionen, während Firestore Batch-Operationen atomar ermöglicht.
Leistung und Skalierbarkeit
Die Echtzeitdatenbank ist erwartungsgemäß recht robust und hat eine geringe Latenz. Datenbanken sind jedoch auf einzelne Regionen beschränkt, vorbehaltlich der zonalen Verfügbarkeit. Firestore hingegen speichert Daten horizontal über mehrere Zonen und Regionen hinweg, um echte globale Verfügbarkeit, Skalierbarkeit und Zuverlässigkeit sicherzustellen. Tatsächlich hat Google versprochen, dass Firestore zuverlässiger sein wird als Realtime Database.
Ein weiterer Mangel der Echtzeitdatenbank ist die Beschränkung auf 100.000 gleichzeitige Benutzer (100.000 gleichzeitige Verbindungen und 1.000 Schreibvorgänge/Sekunde in einer einzelnen Datenbank), wonach Sie Ihre Datenbank fragmentieren (Ihre Datenbank in mehrere Datenbanken aufteilen) müssten, um mehr Benutzer zu unterstützen . Firestore skaliert automatisch über mehrere Instanzen hinweg, ohne dass Sie eingreifen müssen.
Firestore wurde von Grund auf mit Blick auf Skalierbarkeit entwickelt und verfügt über eine neue schematische Architektur, die Daten über mehrere Regionen hinweg repliziert, sich um die Authentifizierung kümmert und andere sicherheitsrelevante Angelegenheiten innerhalb seines clientseitigen SDK behandelt. Das neue Datenmodell ist intuitiver als das von Firebase, ähnelt eher anderen vergleichbaren NoSQL-Datenbanklösungen wie MongoDB und bietet gleichzeitig eine robustere Abfrage-Engine.
Sicherheit
Schließlich verwaltet Realtime Database, wie Sie aus unseren vorherigen Tutorials wissen, die Sicherheit durch kaskadierende Regeln mit separaten Validierungstriggern. Dies funktioniert mit Firebase-Datenbankregeln, bei denen Ihre Daten separat validiert werden. Firestore hingegen bietet ein einfacheres, aber leistungsfähigeres Sicherheitsmodell, das die Vorteile der Cloud Firestore-Sicherheitsregeln und der Identitäts- und Zugriffsverwaltung (IAM) nutzt, wobei die Datenvalidierung automatisch ausgenommen wird.
- Mobile-EntwicklungFirebase-SicherheitsregelnChike Mgbemena
Das Firestore-Datenmodell
Firestore ist eine auf NoSQL-Dokumenten basierende Datenbank, die aus Sammlungen von Dokumenten besteht, von denen jedes Daten enthält. Da es sich um eine NoSQL-Datenbank handelt, erhalten Sie keine Tabellen, Zeilen und andere Elemente, die Sie in einer relationalen Datenbank finden würden, sondern stattdessen Sätze von Schlüssel/Wert-Paaren, die Sie in Dokumenten finden würden.
Sie erstellen Dokumente und Sammlungen implizit, indem Sie einem Dokument Daten zuweisen, und wenn das Dokument oder die Sammlung nicht existiert, wird es automatisch für Sie erstellt, da die Sammlung immer der (erste) Stammknoten sein muss. Hier ist ein einfaches Tasks-Beispielschema des Projekts, an dem Sie in Kürze arbeiten werden, bestehend aus der Tasks-Sammlung sowie zahlreichen Dokumenten, die zwei Felder enthalten, den Namen (String) und ein Flag, ob die Aufgabe erledigt ist (boolean). .
Lassen Sie uns jedes der Elemente zerlegen, damit Sie sie besser verstehen können.
Sammlungen
Als Synonym für Datenbanktabellen in der SQL-Welt enthalten Sammlungen ein oder mehrere Dokumente. Sammlungen müssen die Stammelemente in Ihrem Schema sein und dürfen nur Dokumente enthalten, keine anderen Sammlungen. Sie können jedoch auf ein Dokument verweisen, das wiederum auf Sammlungen (Untersammlungen) verweist.
Im obigen Diagramm besteht eine Aufgabe aus zwei primitiven Feldern (Name und Erledigt) sowie einer Untersammlung (Unteraufgabe), die aus zwei eigenen primitiven Feldern besteht.
Dokumente
Dokumente bestehen aus Schlüssel/Wert-Paaren, wobei die Werte einen der folgenden Typen haben:
- Primitive Felder (wie Strings, Zahlen, boolesche Werte)
- komplexe verschachtelte Objekte (Listen oder Arrays von Primitiven)
- Untersammlungen
Verschachtelte Objekte werden auch Maps genannt und können innerhalb des Dokuments wie folgt dargestellt werden. Das Folgende ist ein Beispiel für ein verschachteltes Objekt bzw. Array:
ID: 2422892 //primitive name: “Remember to buy milk” detail: //nested object notes: "This is a task to buy milk from the store" created: 2017-04-09 due: 2017-04-10 done: false notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"] ...
Weitere Informationen zu den unterstützten Datentypen finden Sie in der Google-Dokumentation zu Datentypen. Als Nächstes richten Sie ein Projekt für die Arbeit mit Cloud Firestore ein.
Einrichten des Projekts
Wenn Sie schon einmal mit Firebase gearbeitet haben, sollte Ihnen vieles davon vertraut sein. Andernfalls müssen Sie ein Konto in Firebase erstellen und den Anweisungen im Abschnitt „Projekt einrichten“ unseres vorherigen Tutorials „Erste Schritte mit der Firebase-Authentifizierung für iOS“ folgen.
Um dieser Anleitung zu folgen, klonen Sie das Projekt-Repository der Anleitung. Fügen Sie als Nächstes die Firestore-Bibliothek von hinzu Folgendes zu Ihrem Podfile hinzufügen :
pod 'Firebase/Core' pod 'Firebase/Firestore'
Geben Sie Folgendes in Ihr Terminal ein, um Ihre Bibliothek zu erstellen:
pod install
Wechseln Sie als Nächstes zu Xcode und öffnen Sie den .xcworkspace Datei. Navigieren Sie zu AppDelegate.swift Datei und geben Sie Folgendes in die application:didFinishLaunchingWithOptions:
ein Methode:
FirebaseApp.configure()
Rufen Sie in Ihrem Browser die Firebase-Konsole auf und wählen Sie die Datenbank aus Tab links.
Stellen Sie sicher, dass Sie die Option Im Testmodus starten auswählen damit Sie keine Sicherheitsprobleme haben, während wir experimentieren, und den Sicherheitshinweis beachten, wenn Sie Ihre App in die Produktion verschieben. Sie können jetzt eine Sammlung und einige Beispieldokumente erstellen.
Hinzufügen einer Sammlung und eines Musterdokuments
Erstellen Sie zunächst eine erste Sammlung, Tasks
, indem Sie Sammlung hinzufügen auswählen Schaltfläche und Benennen der Sammlung, wie unten dargestellt:
Für das erste Dokument lassen Sie die Dokument-ID leer, wodurch automatisch eine ID für Sie generiert wird. Das Dokument besteht einfach aus zwei Feldern: name
und done
.
Speichern Sie das Dokument, und Sie sollten in der Lage sein, die Sammlung und das Dokument zusammen mit der automatisch generierten ID zu bestätigen:
Wenn die Datenbank mit einem Beispieldokument in der Cloud eingerichtet ist, können Sie mit der Implementierung des Firestore SDK in Xcode beginnen.
Erstellen und Arbeiten mit Datenbankreferenzen
Öffnen Sie MasterViewController.swift Datei in Xcode und fügen Sie die folgenden Zeilen hinzu, um die Bibliothek zu importieren:
import Firebase class MasterViewController: UITableViewController { @IBOutlet weak var addButton: UIBarButtonItem! private var documents: [DocumentSnapshot] = [] public var tasks: [Task] = [] private var listener : ListenerRegistration! ...
Hier erstellen Sie einfach eine Listener-Variable, mit der Sie bei einer Änderung in Echtzeit eine Verbindung zur Datenbank auslösen können. Sie erstellen auch einen DocumentSnapshot
Referenz, die den temporären Daten-Snapshot enthält.
Bevor Sie mit dem View-Controller fortfahren, erstellen Sie eine weitere Swift-Datei, Task.swift , die Ihr Datenmodell darstellt:
import Foundation struct Task{ var name:String var done: Bool var id: String var dictionary: [String: Any] { return [ "name": name, "done": done ] } } extension Task{ init?(dictionary: [String : Any], id: String) { guard let name = dictionary["name"] as? String, let done = dictionary["done"] as? Bool else { return nil } self.init(name: name, done: done, id: id) } }
Das obige Code-Snippet enthält eine Convenience-Eigenschaft (Wörterbuch) und eine Methode (init), die das Füllen des Modellobjekts erleichtern. Wechseln Sie zurück zum View-Controller und deklarieren Sie eine globale Setter-Variable, die die Basisabfrage auf die obersten 50 Einträge in der Aufgabenliste beschränkt. Sie entfernen auch den Listener, sobald Sie die Abfragevariable festgelegt haben, wie in didSet
angegeben Eigenschaft unten:
fileprivate func baseQuery() -> Query { return Firestore.firestore().collection("Tasks").limit(to: 50) } fileprivate var query: Query? { didSet { if let listener = listener { listener.remove() } } } override func viewDidLoad() { super.viewDidLoad() self.query = baseQuery() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.listener.remove() }
Lesen von Daten in Echtzeit aus Cloud Firestore
Mit der Dokumentreferenz an Ort und Stelle, in viewWillAppear(_animated: Bool)
, verknüpfen Sie den zuvor erstellten Listener mit den Ergebnissen des Abfrage-Snapshots und rufen Sie eine Liste von Dokumenten ab. Dies erfolgt durch Aufrufen der Firestore-Methode query?.addSnapshotListener
:
self.listener = query?.addSnapshotListener { (documents, error) in guard let snapshot = documents else { print("Error fetching documents results: \(error!)") return } let results = snapshot.documents.map { (document) -> Task in if let task = Task(dictionary: document.data(), id: document.documentID) { return task } else { fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())") } } self.tasks = results self.documents = snapshot.documents self.tableView.reloadData() }
Der obige Abschluss weist die snapshot.documents
zu indem Sie das Array iterativ zuordnen und es in eine neue Task
packen Modellinstanzobjekt für jedes Datenelement im Snapshot. So haben Sie mit wenigen Zeilen erfolgreich alle Aufgaben aus der Cloud eingelesen und den globalen tasks
zugewiesen Reihe.
Um die Ergebnisse anzuzeigen, füllen Sie Folgendes aus TableView
Delegate-Methoden:
override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tasks.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let item = tasks[indexPath.row] cell.textLabel!.text = item.name cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray return cell }
Erstellen Sie in dieser Phase das Projekt und führen Sie es aus. Im Simulator sollten Sie in der Lage sein, Daten in Echtzeit zu beobachten. Fügen Sie Daten über die Firebase-Konsole hinzu und Sie sollten sehen, dass sie sofort im App-Simulator angezeigt werden.
Erstellen, Aktualisieren und Löschen von Daten
Nachdem Sie Inhalte erfolgreich aus dem Backend gelesen haben, erstellen, aktualisieren und löschen Sie als Nächstes Daten. Das nächste Beispiel veranschaulicht, wie Daten aktualisiert werden, indem ein erfundenes Beispiel verwendet wird, bei dem Sie in der App nur ein Element als erledigt markieren können, indem Sie auf die Zelle tippen. Beachten Sie das collection.document(
item.id
).updateData(["done": !item.done])
Closure-Eigenschaft, die einfach auf eine bestimmte Dokument-ID verweist und jedes der Felder im Wörterbuch aktualisiert:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = tasks[indexPath.row] let collection = Firestore.firestore().collection("Tasks") collection.document(item.id).updateData([ "done": !item.done, ]) { err in if let err = err { print("Error updating document: \(err)") } else { print("Document successfully updated") } } tableView.reloadRows(at: [indexPath], with: .automatic) }
Um ein Element zu löschen, rufen Sie das document(
item.id
).delete()
Methode:
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if (editingStyle == .delete){ let item = tasks[indexPath.row] _ = Firestore.firestore().collection("Tasks").document(item.id).delete() } }
Das Erstellen einer neuen Aufgabe beinhaltet das Hinzufügen einer neuen Schaltfläche in Ihrem Storyboard und das Verbinden seiner IBAction
an den View-Controller, wodurch eine addTask(_ sender:)
erstellt wird Methode. Wenn ein Benutzer auf die Schaltfläche drückt, wird ein Warnblatt angezeigt, auf dem der Benutzer einen neuen Aufgabennamen hinzufügen kann:
collection("Tasks").addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false])
Vervollständigen Sie den letzten Teil der App, indem Sie Folgendes eingeben:
@IBAction func addTask(_ sender: Any) { let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert) alertVC.addTextField { (UITextField) in } let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil) alertVC.addAction(cancelAction) //Alert action closure let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in let textFieldReminder = (alertVC.textFields?.first)! as UITextField let db = Firestore.firestore() var docRef: DocumentReference? = nil docRef = db.collection("Tasks").addDocument(data: [ "name": textFieldReminder.text ?? "empty task", "done": false ]) { err in if let err = err { print("Error adding document: \(err)") } else { print("Document added with ID: \(docRef!.documentID)") } } } alertVC.addAction(addAction) present(alertVC, animated: true, completion: nil) }
Erstellen Sie die App noch einmal und führen Sie sie aus. Wenn der Simulator angezeigt wird, versuchen Sie, einige Aufgaben hinzuzufügen und einige als erledigt zu markieren, und testen Sie schließlich die Löschfunktion, indem Sie einige Aufgaben entfernen. Sie können bestätigen, dass die gespeicherten Daten in Echtzeit aktualisiert wurden, indem Sie zu Ihrer Firebase-Datenbankkonsole wechseln und die Sammlung und Dokumente beobachten.
Filtern und zusammengesetzte Abfragen
Bisher haben Sie nur mit einer einfachen Abfrage ohne spezielle Filterfunktionen gearbeitet. Um etwas robustere Abfragen zu erstellen, können Sie nach bestimmten Werten filtern, indem Sie ein whereField
verwenden Klausel:
docRef.whereField(“name”, isEqualTo: searchString)
Sie können Ihre Abfragedaten ordnen und einschränken, indem Sie den Befehl order(by: )
verwenden und limit(to: )
Methoden wie folgt:
docRef.order(by: "name").limit(5)
In der FirebaseDo-App haben Sie bereits von limit
Gebrauch gemacht mit der Basisabfrage. Im obigen Snippet haben Sie auch eine andere Funktion verwendet, zusammengesetzte Abfragen, bei denen sowohl die Reihenfolge als auch das Limit miteinander verkettet sind. Sie können beliebig viele Abfragen verketten, wie im folgenden Beispiel:
docRef .whereField(“name”, isEqualTo: searchString) .whereField(“done”, isEqualTo: false) .order(by: "name") .limit(5)