Mobilkodere har udnyttet Googles Mobile Backend as a Service (MBaaS) platform Firebase Realtime Database i mange år, og de har hjulpet dem med at fokusere på at bygge funktioner til deres apps uden at skulle bekymre sig om back-end-infrastrukturen og databasen. Ved at gøre det nemt at gemme og bevare data i skyen og tage sig af autentificering og sikkerhed, giver Firebase kodere mulighed for at fokusere på klientsiden.
Sidste år annoncerede Google endnu en backend-databaseløsning, Cloud Firestore, bygget fra bunden med løftet om større skalerbarhed og intuitivitet. Dette førte dog til en vis forvirring med hensyn til dets plads i forhold til Googles allerede eksisterende flagskibsprodukt, Firebase Realtime Database. Denne tutorial vil skitsere forskellene mellem de to platforme og de forskellige fordele ved hver. Du lærer, hvordan du arbejder med Firestore-dokumentreferencer, såvel som at læse, skrive, opdatere og slette data i realtid ved at bygge en simpel påmindelsesapp.
Formål med denne vejledning
Dette selvstudie vil udsætte dig for Cloud Firestore. Du lærer, hvordan du udnytter platformen til databasepersistens og synkronisering i realtid. Vi vil dække følgende emner:
- hvad Cloud Firestore er
- Firestore-datamodellen
- opsætning af Cloud Firestore
- oprette og arbejde med Cloud Firestore-referencer
- læse data i realtid fra Cloud Firestore
- oprettelse, opdatering og sletning af data
- filtrering og sammensatte forespørgsler
Antaget viden
Denne vejledning antager, at du har haft en vis eksponering for Firebase og en baggrund udviklet med Swift og Xcode.
Hvad er Cloud Firestore?
Ligesom Firebase Realtime Database giver Firestore mobil- og webudviklere en cloud-løsning på tværs af platforme til at bevare data i realtid, uanset netværksforsinkelse eller internetforbindelse, samt problemfri integration med Google Cloud Platform-produktpakken. Sammen med disse ligheder er der forskellige fordele og ulemper, der adskiller den ene fra den anden.
Datamodel
På et grundlæggende niveau gemmer Realtime Database data som ét stort, monolitisk, hierarkisk JSON-træ, hvorimod Firestore organiserer data i dokumenter og samlinger såvel som undersamlinger. Dette kræver mindre denormalisering. Lagring af data i ét JSON-træ har fordelene ved enkelhed, når det kommer til at arbejde med simple datakrav; det bliver dog mere besværligt i skalaen, når man arbejder med mere komplekse hierarkiske data.
Offline support
Begge produkter tilbyder offline support, aktivt cachelagring af data i køer, når der er latent eller ingen netværksforbindelse - synkroniserer lokale ændringer tilbage til back-end, når det er muligt. Firestore understøtter offline synkronisering til webapps ud over mobilapps, hvorimod Realtime Database kun muliggør mobilsynkronisering.
Forespørgsler og transaktioner
Realtime Database understøtter kun begrænsede sorterings- og filtreringsmuligheder – du kan kun sortere eller filtrere på et ejendomsniveau, men ikke begge dele, i en enkelt forespørgsel. Forespørgsler er også dybe, hvilket betyder, at de returnerer et stort undertræ af resultater. Produktet understøtter kun simple skrive- og transaktionsoperationer, som kræver et afsluttet tilbagekald.
Firestore introducerer på den anden side indeksforespørgsler med sammensat sortering og filtrering, så du kan kombinere handlinger for at skabe kædefiltre og sortering. Du kan også udføre overfladiske forespørgsler, der returnerer undersamlinger i stedet for hele samlingen, du ville få med Realtime Database. Transaktioner er atomare af natur, uanset om du sender en batch-operation eller enkelt, med transaktioner, der gentages automatisk, indtil de afsluttes. Derudover understøtter Realtime Database kun individuelle skrivetransaktioner, hvorimod Firestore giver batch-operationer atomært.
Ydeevne og skalerbarhed
Realtime-databasen er, som du ville forvente, ret robust og har lav latenstid. Databaser er dog begrænset til enkelte regioner, afhængigt af zonetilgængelighed. Firestore rummer på den anden side data horisontalt på tværs af flere zoner og regioner for at sikre ægte global tilgængelighed, skalerbarhed og pålidelighed. Faktisk har Google lovet, at Firestore vil være mere pålidelig end Realtime Database.
En anden mangel ved Realtime-databasen er begrænsningen til 100.000 samtidige brugere (100.000 samtidige forbindelser og 1.000 skrivninger/sekund i en enkelt database), hvorefter du bliver nødt til at sønderdele din database (opdele din database i flere databaser) for at understøtte flere brugere . Firestore skalerer automatisk på tværs af flere forekomster, uden at du behøver at gribe ind.
Firestore er designet fra bunden med skalerbarhed i tankerne og har en ny skematisk arkitektur, der replikerer data på tværs af flere regioner, tager sig af godkendelse og håndterer andre sikkerhedsrelaterede spørgsmål, alt sammen inden for dets klientside-SDK. Dens nye datamodel er mere intuitiv end Firebases og ligner mere andre sammenlignelige NoSQL-databaseløsninger som MongoDB, samtidig med at den giver en mere robust forespørgselsmotor.
Sikkerhed
Endelig styrer Realtime Database, som du ved fra vores tidligere tutorials, sikkerhed gennem kaskaderegler med separate valideringsudløsere. Dette fungerer med Firebase-databaseregler, der validerer dine data separat. Firestore giver på den anden side en enklere og mere kraftfuld sikkerhedsmodel, der udnytter Cloud Firestore Security Rules og Identity and Access Management (IAM), med datavalidering undtaget automatisk.
- MobiludviklingFirebase-sikkerhedsreglerChike Mgbemena
Firestore-datamodellen
Firestore er en NoSQL dokumentbaseret database, der består af samlinger af dokumenter, som hver indeholder data. Da det er en NoSQL-database, får du ikke tabeller, rækker og andre elementer, du ville finde i en relationsdatabase, men i stedet sæt af nøgle/værdi-par, som du ville finde i dokumenter.
Du opretter dokumenter og samlinger implicit ved at tildele data til et dokument, og hvis dokumentet eller samlingen ikke eksisterer, oprettes det automatisk for dig, da samlingen altid skal være rod (første) node. Her er et simpelt Opgave-eksempelskema for det projekt, du skal arbejde på om kort tid, bestående af Opgavesamlingen, samt adskillige dokumenter, der indeholder to felter, navnet (strengen) og et flag for, om opgaven er udført (boolesk) .
Lad os nedbryde hvert af elementerne, så du bedre kan forstå dem.
Samlinger
Synonymt med databasetabeller i SQL-verdenen indeholder samlinger et eller flere dokumenter. Samlinger skal være rodelementerne i dit skema og kan kun indeholde dokumenter, ikke andre samlinger. Du kan dog henvise til et dokument, som igen henviser til samlinger (delsamlinger).
I diagrammet ovenfor består en opgave af to primitive felter (navn og udført) samt en delsamling (delopgave), som består af to egne primitive felter.
Dokumenter
Dokumenter består af nøgle/værdi-par, hvor værdierne har en af følgende typer:
- primitive felter (såsom strenge, tal, boolean)
- komplekse indlejrede objekter (lister eller arrays af primitiver)
- undersamlinger
Indlejrede objekter kaldes også kort og kan repræsenteres som følger i dokumentet. Det følgende er et eksempel på henholdsvis et indlejret objekt og et 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"] ...
For flere oplysninger om de understøttede datatyper, se Googles Data Types dokumentation. Dernæst skal du opsætte et projekt til at arbejde med Cloud Firestore.
Opsætning af projektet
Hvis du har arbejdet med Firebase før, burde meget af dette være bekendt for dig. Ellers skal du oprette en konto i Firebase og følge instruktionerne i sektionen "Konfigurer projektet" i vores tidligere selvstudie, Kom godt i gang med Firebase-godkendelse til iOS.
For at følge med i dette selvstudie skal du klone selvstudieprojektets repos. Medtag derefter Firestore-biblioteket efter tilføjer følgende til din Podfile :
pod 'Firebase/Core' pod 'Firebase/Firestore'
Indtast følgende i din terminal for at bygge dit bibliotek:
pod install
Skift derefter til Xcode og åbn .xcworkspace fil. Naviger til AppDelegate.swift fil og indtast følgende i application:didFinishLaunchingWithOptions:
metode:
FirebaseApp.configure()
I din browser skal du gå til Firebase-konsollen og vælge Database fanen til venstre.
Sørg for at vælge muligheden for at Start i testtilstand så du ikke har nogen sikkerhedsproblemer, mens vi eksperimenterer, og følg sikkerhedsmeddelelsen, når du flytter din app i produktion. Du er nu klar til at oprette en samling og nogle eksempler på dokumenter.
Tilføjelse af en samling og et eksempeldokument
For at starte skal du oprette en indledende samling, Tasks
, ved at vælge Tilføj samling knappen og navngivning af samlingen, som illustreret nedenfor:
For det første dokument vil du lade dokument-id'et stå tomt, hvilket automatisk genererer et id til dig. Dokumentet vil blot bestå af to felter: name
og done
.
Gem dokumentet, og du skulle være i stand til at bekræfte indsamlingen og dokumentet sammen med det automatisk genererede ID:
Med databasen sat op med et eksempeldokument i skyen, er du klar til at begynde at implementere Firestore SDK i Xcode.
Oprettelse og arbejde med databasereferencer
Åbn MasterViewController.swift fil i Xcode og tilføj følgende linjer for at importere biblioteket:
import Firebase class MasterViewController: UITableViewController { @IBOutlet weak var addButton: UIBarButtonItem! private var documents: [DocumentSnapshot] = [] public var tasks: [Task] = [] private var listener : ListenerRegistration! ...
Her opretter du blot en lyttevariabel, der giver dig mulighed for at udløse en forbindelse til databasen i realtid, når der er en ændring. Du opretter også et DocumentSnapshot
reference, der vil holde det midlertidige datasnapshot.
Før du fortsætter med visningscontrolleren, skal du oprette en anden swift-fil, Task.swift , som vil repræsentere din datamodel:
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) } }
Kodestykket ovenfor inkluderer en bekvemmelighedsegenskab (ordbog) og metode (init), der vil gøre det lettere at udfylde modelobjektet. Skift tilbage til visningscontrolleren, og erklær en global indstillervariabel, som vil begrænse basisforespørgslen til de 50 bedste poster på opgavelisten. Du vil også fjerne lytteren, når du har indstillet forespørgselsvariablen, som angivet i didSet
ejendom nedenfor:
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() }
Læsning af data i realtid fra Cloud Firestore
Med dokumentreferencen på plads i viewWillAppear(_animated: Bool)
, tilknyt den lytter, du oprettede tidligere, med resultaterne af forespørgselsøjebliksbilledet, og hent en liste over dokumenter. Dette gøres ved at kalde Firestore-metoden 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() }
Lukningen ovenfor tildeler snapshot.documents
ved at kortlægge arrayet iterativt og pakke det til en ny Task
modelforekomstobjekt for hvert dataelement i øjebliksbilledet. Så med blot et par linjer har du med succes læst alle opgaverne fra skyen og tildelt dem til de globale tasks
array.
For at vise resultaterne skal du udfylde følgende TableView
delegere metoder:
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 }
På dette stadium skal du bygge og køre projektet, og i simulatoren bør du kunne observere data, der vises i realtid. Tilføj data via Firebase-konsollen, og du bør se dem vises øjeblikkeligt i app-simulatoren.
Oprettelse, opdatering og sletning af data
Efter at have læst indhold fra back-end, vil du derefter oprette, opdatere og slette data. Det næste eksempel vil illustrere, hvordan du opdaterer data ved hjælp af et konstrueret eksempel, hvor appen kun lader dig markere et element som udført ved at trykke på cellen. Bemærk collection.document(
item.id
).updateData(["done": !item.done])
closure-egenskab, som blot refererer til et specifikt dokument-id, og opdaterer hvert af felterne i ordbogen:
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) }
For at slette et element skal du kalde document(
item.id
).delete()
metode:
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() } }
Oprettelse af en ny opgave involverer tilføjelse af en ny knap i dit Storyboard og tilslutning af dens IBAction
til visningscontrolleren ved at oprette en addTask(_ sender:)
metode. Når en bruger trykker på knappen, vil den vise et advarselsark, hvor brugeren kan tilføje et nyt opgavenavn:
collection("Tasks").addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false])
Fuldfør den sidste del af appen ved at indtaste følgende:
@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) }
Byg og kør appen igen, og når simulatoren dukker op, kan du prøve at tilføje et par opgaver, samt markere nogle som udførte, og til sidst teste slettefunktionen ved at fjerne nogle opgaver. Du kan bekræfte, at de lagrede data er blevet opdateret i realtid ved at skifte til din Firebase-databasekonsol og observere indsamlingen og dokumenterne.
Filtrering og sammensatte forespørgsler
Hidtil har du kun arbejdet med en simpel forespørgsel, uden nogen specifikke filtreringsmuligheder. For at oprette lidt mere robuste forespørgsler kan du filtrere efter specifikke værdier ved at bruge et whereField
klausul:
docRef.whereField(“name”, isEqualTo: searchString)
Du kan bestille og begrænse dine forespørgselsdata ved at bruge order(by: )
og limit(to: )
metoder som følger:
docRef.order(by: "name").limit(5)
I FirebaseDo-appen har du allerede brugt limit
med basisforespørgslen. I ovenstående uddrag gjorde du også brug af en anden funktion, sammensatte forespørgsler, hvor både rækkefølgen og grænsen er kædet sammen. Du kan sammenkæde så mange forespørgsler, som du vil, f.eks. i følgende eksempel:
docRef .whereField(“name”, isEqualTo: searchString) .whereField(“done”, isEqualTo: false) .order(by: "name") .limit(5)