Guida introduttiva a Cloud Firestore per iOS

I codificatori mobili hanno approfittato per molti anni della piattaforma di database mobile in tempo reale di Google Backend as a Service (MBaaS) di Google, aiutandoli a concentrarsi sulla creazione di funzionalità per le loro app senza doversi preoccupare dell'infrastruttura e del database di back-end. Facilitando la memorizzazione e la persistenza dei dati nel cloud e prendendosi cura dell'autenticazione e della sicurezza, Firebase consente ai programmatori di concentrarsi sul lato client. 

L'anno scorso, Google ha annunciato l'ennesima soluzione di database back-end, Cloud Firestore, costruita da zero con la promessa di maggiore scalabilità e intuitività. Tuttavia, questo ha introdotto qualche confusione sulla sua posizione in relazione al già esistente prodotto di punta di Google, Firebase Realtime Database. Questo tutorial descriverà le differenze tra le due piattaforme e i distinti vantaggi di ciascuna. Imparerai come lavorare con i riferimenti ai documenti di Firestore, oltre a leggere, scrivere, aggiornare e cancellare i dati in tempo reale, creando una semplice app per i promemoria.

Obiettivi di questo tutorial

Questo tutorial ti esporrà a Cloud Firestore. Imparerai come sfruttare la piattaforma per la persistenza e la sincronizzazione dei database in tempo reale. Tratteremo i seguenti argomenti:

  • cos'è Cloud Firestore
  • il modello di dati Firestore
  • creare Cloud Firestore
  • creare e lavorare con i riferimenti di Cloud Firestore
  • lettura dei dati in tempo reale da Cloud Firestore
  • creazione, aggiornamento e cancellazione dei dati
  • filtraggio e query composte

Conoscenza presunta

Questo tutorial presume che tu abbia avuto una certa esposizione a Firebase e uno sfondo in via di sviluppo con Swift e Xcode.

Che cos'è Cloud Firestore?

Come Firebase Realtime Database, Firestore offre agli sviluppatori mobili e web una soluzione cloud multipiattaforma per mantenere i dati in tempo reale, indipendentemente dalla latenza della rete o dalla connettività Internet, oltre alla perfetta integrazione con la suite di prodotti Google Cloud Platform. Insieme a queste somiglianze, ci sono diversi vantaggi e svantaggi che differenziano l'uno dall'altro. 

Modello di dati

A livello fondamentale, Realtime Database archivia i dati come un unico albero JSON monolitico, gerarchico, mentre Firestore organizza i dati in documenti e raccolte, nonché sotto-raccolte. Ciò richiede meno denormalizzazione. La memorizzazione dei dati in un albero JSON ha i vantaggi della semplicità quando si tratta di lavorare con requisiti di dati semplici; tuttavia, diventa più ingombrante su scala quando si lavora con dati gerarchici più complessi. 

Supporto offline

Entrambi i prodotti offrono supporto offline, memorizzando attivamente i dati nelle code in caso di latenza o assenza di connettività di rete, sincronizzando le modifiche locali sul back-end quando possibile. Firestore supporta la sincronizzazione offline per le app Web oltre alle app mobili, mentre il database Realtime consente solo la sincronizzazione mobile.

Query e transazioni 

Il database in tempo reale supporta solo funzionalità di ordinamento e filtraggio limitate: è possibile ordinare o filtrare solo a livello di proprietà, ma non entrambi, in una singola query. Anche le query sono profonde, nel senso che restituiscono un grande sotto-albero di risultati. Il prodotto supporta solo operazioni di scrittura e transazioni semplici che richiedono un callback di completamento. 

Firestore, d'altra parte, introduce le query di indice con ordinamento e filtraggio composti, consentendo di combinare le azioni per creare filtri e ordinamenti a catena. È inoltre possibile eseguire query poco profonde restituendo sotto-raccolte al posto dell'intera raccolta che si otterrebbe con Realtime Database. Le transazioni sono di natura atomica, indipendentemente dal fatto che si invii un'operazione batch o singola, con transazioni che si ripetono automaticamente fino alla conclusione. Inoltre, Realtime Database supporta solo transazioni di scrittura individuali, mentre Firestore consente di eseguire operazioni batch in modo atomico.

Prestazioni e scalabilità

Il Database Realtime, come ci si aspetterebbe, è abbastanza robusto e ha una bassa latenza. Tuttavia, i database sono limitati a singole regioni, soggetti a disponibilità zonale. Firestore, d'altra parte, ospita i dati orizzontalmente su più zone e regioni per garantire la reale disponibilità globale, scalabilità e affidabilità. Infatti, Google ha promesso che Firestore sarà più affidabile del Database Realtime. 

Un altro inconveniente del Realtime Database è la limitazione a 100.000 utenti simultanei (100.000 connessioni simultanee e 1.000 scritture / secondo in un singolo database) dopo il quale dovresti dividere il tuo database (dividere il tuo database in più database) per supportare più utenti . Firestore scala automaticamente su più istanze senza che tu debba intervenire. 

Progettato partendo da zero per la scalabilità, Firestore ha una nuova architettura schematica che replica i dati su più regioni, si occupa dell'autenticazione e gestisce altri aspetti relativi alla sicurezza all'interno del proprio SDK lato client. Il suo nuovo modello di dati è più intuitivo di Firebase, più simile ad altre soluzioni di database NoSQL comparabili come MongoDB, pur fornendo un motore di query più robusto. 

Sicurezza 

Infine, Realtime Database, come sapete dalle nostre precedenti esercitazioni, gestisce la sicurezza attraverso regole a cascata con trigger di convalida separati. Funziona con le regole del database Firebase, convalidando i dati separatamente. Firestore, d'altra parte, fornisce un modello di sicurezza più semplice ma più potente sfruttando le regole di sicurezza di Cloud Firestore e IAM (Identity and Access Management), con la convalida dei dati esclusa automaticamente.

Il modello di dati Firestore

Firestore è un database basato su documenti NoSQL, costituito da raccolte di documenti, ognuno dei quali contiene dati. Essendo un database NoSQL, non otterrai tabelle, righe e altri elementi che troveresti in un database relazionale, ma set di coppie chiave / valore che troveresti nei documenti. 

I documenti e le raccolte vengono creati implicitamente mediante l'assegnazione di dati a un documento e, se il documento o la raccolta non esistono, verranno creati automaticamente per te, poiché la raccolta deve sempre essere il nodo (primo) radice. Ecco un semplice schema di esempio di Task del progetto su cui lavorerai a breve, composto dalla raccolta di attività, oltre a numerosi documenti contenenti due campi, il nome (stringa) e un flag per indicare se l'attività è stata eseguita (booleano).

Scomporre ognuno degli elementi in modo da poterli comprendere meglio. 

collezioni

Sinonimo di tabelle di database nel mondo SQL, le raccolte contengono uno o più documenti. Le raccolte devono essere gli elementi radice del tuo schema e possono contenere solo documenti, non altre raccolte. Tuttavia, puoi fare riferimento a un documento che a sua volta fa riferimento a raccolte (sotto-raccolte).

Nel diagramma sopra, un'attività consiste di due campi primitivi (nome e fatto) e una sotto-raccolta (sotto-attività) che consiste di due campi primitivi propri. 

Documenti

I documenti sono costituiti da coppie chiave / valore, con i valori che hanno uno dei seguenti tipi: 

  • campi primitivi (come stringhe, numeri, booleani)
  • oggetti nidificati complessi (liste o matrici di primitivi)
  • sub-collezioni

Gli oggetti nidificati sono anche chiamati mappe e possono essere rappresentati come segue, all'interno del documento. Di seguito è riportato un esempio di oggetto e array nidificati, rispettivamente:

ID: 2422892 // nome primitivo: "Ricordati di comprare il latte" dettaglio: // note dell'oggetto annidato: "Questo è un compito per comprare il latte dal negozio" creato: 2017-04-09 a causa: 2017-04-10 done: notifica falsa: ["2F22-89R2", "L092-G623", "H00V-T4S1"] ... 

Per ulteriori informazioni sui tipi di dati supportati, consultare la documentazione relativa ai tipi di dati di Google. Quindi, configurerai un progetto per lavorare con Cloud Firestore.

Impostazione del progetto

Se hai già lavorato con Firebase, molto di questo dovrebbe esserti familiare. In caso contrario, dovrai creare un account in Firebase e seguire le istruzioni nella sezione "Configura il progetto" del nostro tutorial precedente, Guida introduttiva all'autenticazione Firebase per iOS . 

Per seguire questo tutorial, clonare il repository del progetto tutorial. Successivamente, includi la libreria Firestore diaggiungendo quanto segue al tuo Podfile:

pod 'Firebase / Core' pod 'Firebase / Firestore'

Inserisci quanto segue nel tuo terminale per creare la tua libreria:

installazione pod

Quindi, passare a Xcode e aprire il .xcworkspace file. Vai a AppDelegate.swift file e inserire il seguente all'interno del file applicazione: didFinishLaunchingWithOptions: metodo:

FirebaseApp.configure ()

Nel browser, vai alla console Firebase e seleziona il Banca dati scheda a sinistra. 

Assicurati di selezionare l'opzione per Inizia in modalità test in modo da non avere problemi di sicurezza durante l'esperimento e prestare attenzione alla notifica di sicurezza quando trasferisci la tua app in produzione. Ora sei pronto per creare una raccolta e alcuni documenti di esempio.

Aggiunta di una raccolta e un documento di esempio

Per iniziare, crea una raccolta iniziale, Compiti, selezionando il Aggiungi raccolta pulsante e nominando la raccolta, come illustrato di seguito:

Per il primo documento, lascerai vuoto l'ID del documento, che genererà automaticamente un ID per te. Il documento consisterà semplicemente in due campi: nome e fatto.

Salva il documento e dovresti essere in grado di confermare la raccolta e il documento insieme all'ID generato automaticamente:

Con il database configurato con un documento di esempio nel cloud, sei pronto per iniziare a implementare l'SDK Firestore in Xcode.

Creazione e utilizzo dei riferimenti del database

Apri il MasterViewController.swift file in Xcode e aggiungi le seguenti linee per importare la libreria:

importare la classe Firebase MasterViewController: UITableViewController @IBOutlet weak var addButton: UIBarButtonItem! private var documents: [DocumentSnapshot] = [] public var tasks: [Task] = [] private var listener: ListenerRegistration! ... 

Qui stai semplicemente creando una variabile listener che ti permetterà di attivare una connessione al database in tempo reale quando c'è una modifica. Stai anche creando un DocumentSnapshot riferimento che conserverà l'istantanea dei dati temporanei.

Prima di continuare con il controller della vista, crea un altro file swift, Task.swift, che rappresenterà il tuo modello di dati:

import Struttura 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"] come? Bool else return nil self.init (nome: name, done: done, id: id)

Lo snippet di codice sopra include una proprietà di convenienza (dizionario) e un metodo (init) che renderanno più facile popolare l'oggetto del modello. Tornare al controller della vista e dichiarare una variabile setter globale che vincola la query di base alle prime 50 voci nell'elenco delle attività. Rimuoverai anche il listener dopo aver impostato la variabile query, come indicato nel file didSet proprietà sottostante:

fileprivate func baseQuery () -> Query return Firestore.firestore (). collection ("Tasks"). limit (to: 50) file var query var: Query? didSet if let listener = listener listener.remove () override func viewDidLoad () super.viewDidLoad () self.query = baseQuery () override func viewWillDisappear (_ animato: Bool) super.viewWillDisappear ( animato) self.listener.remove ()

Lettura dei dati in tempo reale da Firestore Cloud

Con il riferimento del documento in atto, in viewWillAppear (_animated: Bool), associare il listener creato in precedenza con i risultati dell'istantanea della query e recuperare un elenco di documenti. Questo viene fatto chiamando il metodo Firestore interrogare? .addSnapshotListener:

self.listener = query? .addSnapshotListener (documenti, errore) in guardia lascia snapshot = documenti else print ("Errore nel recupero dei risultati dei documenti: \ (errore!)") return let results = snapshot.documents.map (document ) -> Task in if let task = Task (dizionario: document.data (), id: document.documentID) return task else fatalError ("Impossibile inizializzare il tipo \ (Task.self) con dizionario \ (documento. data ()) ") self.tasks = results self.documents = snapshot.documents self.tableView.reloadData ()

La chiusura sopra assegna il snapshot.documents mappando l'array in modo iterativo e avvolgendolo in un nuovo Compito modello di istanza per ogni elemento di dati nell'istantanea. Quindi con poche righe, hai letto con successo tutte le attività dal cloud e le hai assegnate al globale compitischieramento. 

Per visualizzare i risultati, compilare quanto segueTableViewmetodi delegati:

override func numberOfSections (in tableView: UITableView) -> Int return 1 override func tableView (_ tableView: UITableView, numberOfRowsInSection sezione: Int) -> Int return tasks.count sostituisce func tableView (_ tableView: UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell let cell = tableView.dequeueReusableCell (withIdentifier: "Cell", per: indexPath) let item = tasks [indexPath.row] cell.textLabel! .Text = item.name cell.textLabel! .TextColor = item.done == false? UIColor.black: UIColor.lightGray restituisce la cella

In questa fase, costruisci ed esegui il progetto e nel simulatore dovresti essere in grado di osservare i dati che appaiono in tempo reale. Aggiungi dati tramite la console di Firebase e dovresti vederlo apparire istantaneamente nel simulatore dell'app. 

Creazione, aggiornamento ed eliminazione dei dati

Dopo aver letto con successo il contenuto dal back-end, in seguito creerai, aggiorni e cancelli i dati. Il prossimo esempio illustrerà come aggiornare i dati, utilizzando un esempio forzato in cui l'app ti consente solo di contrassegnare un elemento come fatto toccando la cella. Notare la collection.document (numero identificativo dell'oggetto) .updateData (["done":! item.done]) proprietà di chiusura, che fa semplicemente riferimento a un ID documento specifico, aggiornando ciascuno dei campi nel dizionario:

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 se lasciato err = err print ("Errore nell'aggiornamento del documento: \ (err)") else print ("Documento aggiornato con successo") tableView.reloadRows (a: [indexPath], con: .automatic)

Per eliminare un elemento, chiama il documento(numero identificativo dell'oggetto).Elimina() metodo:

override func tableView (_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool return true override func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if (editStyle == .delete)  let item = tasks [indexPath.row] _ = Firestore.firestore (). collection ("Tasks"). document (item.id) .delete ()

La creazione di una nuova attività comporterà l'aggiunta di un nuovo pulsante nella Storyboard e la connessione IBAction al controller della vista, creando un addTask (_ mittente :) metodo. Quando un utente preme il pulsante, verrà visualizzato un foglio di avviso in cui l'utente può aggiungere un nuovo nome dell'attività:

collection ("Tasks"). addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false]) 

Completa la parte finale dell'app inserendo quanto segue:

@IBAction func addTask (_ mittente: Qualsiasi) let alertVC: UIAlertController = UIAlertController (titolo: "Nuova attività", messaggio: "Che cosa vuoi ricordare?", PreferredStyle: .alert) alertVC.addTextField (UITextField) in  let cancelAction = UIAlertAction.init (titolo: "Annulla", style: .destructive, handler: nil) alertVC.addAction (cancelAction) // Alert action closure let addAction = UIAlertAction.init (titolo: "Aggiungi", style:. default) (UIAlertAction) -> Void in let textFieldReminder = (alertVC.textFields? .first)! come UITextField lascia db = Firestore.firestore () var docRef: DocumentReference? = nil docRef = db.collection ("Tasks"). addDocument (dati: ["nome": textFieldReminder.text ?? "empty task", "done": false]) err in if let err = err stampa ( "Errore nell'aggiunta del documento: \ (err)") else print ("Documento aggiunto con ID: \ (docRef! .DocumentID)") alertVC.addAction (addAction) presente (alertVC, animato: true, completamento: nil)

Costruisci ed esegui l'app ancora una volta e, quando viene visualizzato il simulatore, prova ad aggiungere alcune attività, a contrassegnarne alcune come completate e infine a testare la funzione di eliminazione rimuovendo alcune attività. Puoi confermare che i dati memorizzati sono stati aggiornati in tempo reale passando alla console del database Firebase e osservando la raccolta e i documenti.

Filtro e query composte

Finora, hai lavorato solo con una query semplice, senza alcuna capacità di filtraggio specifica. Per creare query leggermente più affidabili, puoi filtrare in base a valori specifici facendo uso di a whereField clausola:

docRef.whereField ("nome", isEqualTo: searchString)

È possibile ordinare e limitare i dati della query, facendo uso di ordinato da: ) e limita a: ) metodi come segue:

docRef.order (per: "nome"). limit (5)

Nell'app FirebaseDo hai già fatto uso di limite con la query di base. Nel frammento di cui sopra, hai anche fatto uso di un'altra funzione, le query composte, in cui sia l'ordine che il limite sono concatenati. Puoi concatenare tutte le query che vuoi, come nell'esempio seguente:

docRef .whereField ("name", isEqualTo: searchString) .whereField ("done", isEqualTo: false) .order (di: "name") .limit (5)

Conclusione

In questo tutorial, hai esplorato il nuovo prodotto MBaaS di Google, Cloud Firestore, e nel processo hai creato una semplice app promemoria delle attività che dimostra quanto sia facile per te persistere, sincronizzare e interrogare i tuoi dati nel cloud. Hai imparato a conoscere la struttura dello schema dei dati di Firestore rispetto al Database in tempo reale di Firebase e come leggere e scrivere i dati in tempo reale, nonché aggiornare e cancellare i dati. Hai anche imparato come eseguire query semplici e composte e come filtrare i dati. 

Cloud Firestore è stato creato con l'obiettivo di fornire la robustezza del database in tempo reale di Firebase senza molte delle limitazioni che gli sviluppatori mobili dovevano sopportare, in particolare per quanto riguarda la scalabilità e l'interrogazione. Abbiamo solo scalfito la superficie di ciò che è possibile ottenere con Firestore, e certamente vale la pena di esplorare alcuni dei concetti più avanzati, come la impaginazione dei dati con i cursori di query, la gestione degli indici e la protezione dei dati.