L'API Codable di Swift, introdotta in Swift 4, ci consente di sfruttare la potenza del compilatore per semplificare la mappatura dei dati dai formati serializzati ai tipi Swift.
Potresti aver utilizzato Codable per mappare i dati di un'API web al modello di dati della tua app (e viceversa), ma è molto più flessibile.
In questa guida, esamineremo come Codable può essere utilizzato per mappare i dati da Cloud Firestore ai tipi Swift e viceversa.
Quando recuperi un documento da Cloud Firestore, la tua app riceve un dizionario di coppie chiave/valore (o un array di dizionari, se utilizzi una delle operazioni che restituisce più documenti).
Ora puoi certamente continuare a utilizzare direttamente i dizionari in Swift, che offrono un'ottima flessibilità che potrebbe essere esattamente ciò che richiede il tuo caso d'uso. Tuttavia, questo approccio non è sicuro dal punto di vista del tipo ed è facile introdurre bug difficili da rilevare scrivendo male i nomi degli attributi o dimenticando di mappare il nuovo attributo aggiunto dal team quando ha rilasciato la nuova funzionalità la scorsa settimana.
In passato, molti sviluppatori hanno risolto questi problemi implementando un semplice livello di mappatura che consentiva di mappare i dizionari ai tipi Swift. Tuttavia, la maggior parte di queste implementazioni si basa sulla specifica manuale della mappatura tra i documenti Cloud Firestore e i tipi corrispondenti del modello di dati dell'app.
Con il supporto di Cloud Firestore per l'API Codable di Swift, questa operazione diventa molto più semplice:
- Non dovrai più implementare manualmente alcun codice di mappatura.
- È facile definire la mappatura degli attributi con nomi diversi.
- Supporta molti tipi di Swift.
- Inoltre, è facile aggiungere il supporto per la mappatura dei tipi personalizzati.
- Il bello è che, per i modelli di dati semplici, non dovrai scrivere alcun codice di mappatura.
Dati di mappatura
Cloud Firestore archivia i dati in documenti che mappano le chiavi ai valori. Per recuperare i dati da un singolo documento, possiamo chiamare DocumentSnapshot.data()
, che restituisce un dizionario che mappa i nomi dei campi a un Any
:func data() -> [String : Any]?
.
Ciò significa che possiamo utilizzare la sintassi di indice di Swift per accedere a ogni singolo campo.
import FirebaseFirestore
#warning("DO NOT MAP YOUR DOCUMENTS MANUALLY. USE CODABLE INSTEAD.")
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
let id = document.documentID
let data = document.data()
let title = data?["title"] as? String ?? ""
let numberOfPages = data?["numberOfPages"] as? Int ?? 0
let author = data?["author"] as? String ?? ""
self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author)
}
}
}
}
Anche se potrebbe sembrare semplice e facile da implementare, questo codice è fragile, difficile da gestire e soggetto a errori.
Come puoi vedere, stiamo facendo delle supposizioni sui tipi di dati dei campi del documento. Potrebbero essere corretti o meno.
Ricorda che, poiché non esiste uno schema, puoi aggiungere facilmente un nuovo documento alla raccolta e scegliere un tipo diverso per un campo. Potresti scegliere per sbaglio una stringa per il campo numberOfPages
, il che causerebbe un problema di mappatura difficile da trovare. Inoltre, dovrai aggiornare il codice di mappatura ogni volta che viene aggiunto un nuovo campo, il che è piuttosto complicato.
Inoltre, non stiamo sfruttando il sistema di tipi forti di Swift, che conosce esattamente il tipo corretto per ciascuna delle proprietà di Book
.
Che cos'è Codable?
Secondo la documentazione di Apple, Codable è "un tipo che può convertirsi in una rappresentazione esterna e viceversa". Infatti, Codable è un alias di tipo per i protocolli Encodable e Decodable. Se rendi conforme un tipo Swift a questo protocollo, il compilatore sintetizzerà il codice necessario per codificare/decodificare un'istanza di questo tipo da un formato serializzato, come JSON.
Un tipo semplice per memorizzare i dati di un libro potrebbe avere il seguente aspetto:
struct Book: Codable {
var title: String
var numberOfPages: Int
var author: String
}
Come puoi vedere, l'adeguamento del tipo a Codable è minimamente invasivo. Abbiamo solo dovuto aggiungere la conformità al protocollo; non sono state necessarie altre modifiche.
Ora possiamo codificare facilmente un libro in un oggetto JSON:
do {
let book = Book(title: "The Hitchhiker's Guide to the Galaxy",
numberOfPages: 816,
author: "Douglas Adams")
let encoder = JSONEncoder()
let data = try encoder.encode(book)
}
catch {
print("Error when trying to encode book: \(error)")
}
La decodifica di un oggetto JSON in un'istanza Book
avviene nel seguente modo:
let decoder = JSONDecoder()
let data = /* fetch data from the network */
let decodedBook = try decoder.decode(Book.self, from: data)
Mappatura a e da tipi semplici nei Cloud Firestoredocumenti
utilizzando Codable
Cloud Firestore supporta un'ampia gamma di tipi di dati, dalle stringhe semplici alle mappe nidificate. La maggior parte di questi corrisponde direttamente ai tipi integrati di Swift. Prima di esaminare quelli più complessi, diamo un'occhiata alla mappatura di alcuni tipi di dati semplici.
Per mappare i documenti Cloud Firestore ai tipi Swift:
- Assicurati di aver aggiunto il framework
FirebaseFirestore
al progetto. Puoi utilizzare Swift Package Manager o CocoaPods per farlo. - Importa
FirebaseFirestore
nel file Swift. - Rendi conforme il tuo tipo a
Codable
. - (Facoltativo, se vuoi utilizzare il tipo in una visualizzazione
List
) Aggiungi una proprietàid
al tipo e utilizza@DocumentID
per indicare a Cloud Firestore di mapparla all'ID documento. Ne parleremo più in dettaglio di seguito. - Utilizza
documentReference.data(as: )
per mappare un riferimento a un documento a un tipo Swift. - Utilizza
documentReference.setData(from: )
per mappare i dati dai tipi Swift a un Cloud Firestore documento. - (Facoltativo, ma vivamente consigliato) Implementa una gestione degli errori adeguata.
Aggiorniamo di conseguenza il tipo Book
:
struct Book: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
Poiché questo tipo era già codificabile, dovevamo solo aggiungere la proprietà id
e annotarla con il wrapper della proprietà @DocumentID
.
Prendendo lo snippet di codice precedente per il recupero e la mappatura di un documento, possiamo sostituire tutto il codice di mappatura manuale con una singola riga:
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.book = try document.data(as: Book.self)
}
catch {
print(error)
}
}
}
}
}
Puoi scrivere questo codice in modo ancora più conciso specificando il tipo di documento
quando chiami getDocument(as:)
. Verrà eseguita la mappatura per te e verrà restituito un tipo Result
contenente il documento mappato o un errore nel caso in cui la decodifica non sia riuscita:
private func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument(as: Book.self) { result in
switch result {
case .success(let book):
// A Book value was successfully initialized from the DocumentSnapshot.
self.book = book
self.errorMessage = nil
case .failure(let error):
// A Book value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
Aggiornare un documento esistente è semplice come chiamare
documentReference.setData(from: )
. È incluso un codice di gestione degli errori di base per salvare un'istanza Book
:
func updateBook(book: Book) {
if let id = book.id {
let docRef = db.collection("books").document(id)
do {
try docRef.setData(from: book)
}
catch {
print(error)
}
}
}
Quando aggiungi un nuovo documento, Cloud Firestore si occuperà automaticamente di assegnare un nuovo ID documento al documento. Questo funziona anche quando l'app è attualmente offline.
func addBook(book: Book) {
let collectionRef = db.collection("books")
do {
let newDocReference = try collectionRef.addDocument(from: self.book)
print("Book stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
Oltre a mappare tipi di dati semplici, Cloud Firestore supporta un numero di altri tipi di dati, alcuni dei quali sono tipi strutturati che puoi utilizzare per creare oggetti nidificati all'interno di un documento.
Tipi personalizzati nidificati
La maggior parte degli attributi che vogliamo mappare nei nostri documenti sono valori semplici, come il titolo del libro o il nome dell'autore. Ma che succede quando dobbiamo memorizzare un oggetto più complesso? Ad esempio, potremmo voler memorizzare gli URL della copertina del libro in risoluzioni diverse.
Il modo più semplice per farlo in Cloud Firestore è utilizzare una mappa:
Quando scriviamo la corrispondente struttura Swift, possiamo sfruttare il fatto che Cloud Firestore supporta gli URL: quando memorizziamo un campo contenente un URL, questo verrà convertito in una stringa e viceversa:
struct CoverImages: Codable {
var small: URL
var medium: URL
var large: URL
}
struct BookWithCoverImages: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var cover: CoverImages?
}
Nota come abbiamo definito una struttura, CoverImages
, per la mappa di copertina nel
documento Cloud Firestore. Contrassegnando la proprietà cover su
BookWithCoverImages
come facoltativa, possiamo gestire il fatto che alcuni
documenti potrebbero non contenere un attributo cover.
Se ti stai chiedendo perché non è presente uno snippet di codice per il recupero o l'aggiornamento dei dati, ti farà piacere sapere che non è necessario modificare il codice per la lettura o la scrittura da/a Cloud Firestore: tutto funziona con il codice che abbiamo scritto nella sezione iniziale.
Array
A volte, vogliamo memorizzare una raccolta di valori in un documento. I generi di un libro sono un buon esempio: un libro come Guida galattica per autostoppisti potrebbe rientrare in diverse categorie, in questo caso "Fantascienza" e "Commedia":
In Cloud Firestore, possiamo modellare questo utilizzando un array di valori. Questa operazione è supportata per qualsiasi tipo codificabile (ad es. String
, Int
e così via). Di seguito viene mostrato come aggiungere un array di generi al nostro modello Book
:
public struct BookWithGenre: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var genres: [String]
}
Poiché funziona per qualsiasi tipo codificabile, possiamo utilizzare anche i tipi personalizzati. Immaginiamo di voler memorizzare un elenco di tag per ogni libro. Oltre al nome del tag, vogliamo memorizzare anche il colore del tag, come segue:
Per memorizzare i tag in questo modo, è sufficiente implementare una struct Tag
per rappresentare un tag e renderlo codificabile:
struct Tag: Codable, Hashable {
var title: String
var color: String
}
E così, possiamo memorizzare un array di Tags
nei nostri documenti Book
.
struct BookWithTags: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var tags: [Tag]
}
Una breve nota sulla mappatura degli ID documento
Prima di passare alla mappatura di altri tipi, parliamo brevemente della mappatura degli ID documento.
Abbiamo utilizzato il wrapper della proprietà @DocumentID
in alcuni degli esempi precedenti
per mappare l'ID documento dei nostri documenti Cloud Firestore alla proprietà id
dei nostri tipi Swift. Questo è importante per diversi motivi:
- Ci aiuta a sapere quale documento aggiornare nel caso in cui l'utente apporti modifiche locali.
List
di SwiftUI richiede che i relativi elementi sianoIdentifiable
per impedire che saltino quando vengono inseriti.
Vale la pena sottolineare che un attributo contrassegnato come @DocumentID
non verrà codificato dall'encoder di Cloud Firestore durante la scrittura del documento. Questo accade perché l'ID documento non è un attributo del documento stesso, quindi scriverlo nel documento sarebbe un errore.
Quando si utilizzano tipi nidificati (ad esempio l'array di tag in Book
in un
esempio precedente di questa guida), non è necessario aggiungere una proprietà @DocumentID
: le proprietà nidificate fanno parte del documento Cloud Firestore e
non costituiscono un documento separato. Di conseguenza, non è necessario un ID documento.
Date e ore
Cloud Firestore ha un tipo di dati integrato per la gestione di date e ore e, grazie al supporto di Cloud Firestore per Codable, è facile utilizzarli.
Diamo un'occhiata a questo documento che rappresenta la madre di tutti i linguaggi di programmazione, Ada, inventata nel 1843:
Un tipo Swift per la mappatura di questo documento potrebbe avere il seguente aspetto:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
}
Non possiamo uscire da questa sezione relativa a date e orari senza parlare di @ServerTimestamp
. Questo wrapper delle proprietà è molto utile per gestire i timestamp nella tua app.
In qualsiasi sistema distribuito, è probabile che gli orologi dei singoli sistemi non siano completamente sincronizzati in ogni momento. Potresti pensare che non sia un gran problema, ma immagina le implicazioni di un orologio leggermente fuori sincrono per un sistema di negoziazione di azioni: anche una deviazione di un millisecondo potrebbe comportare una differenza di milioni di dollari durante l'esecuzione di una transazione.
Cloud Firestore gestisce gli attributi contrassegnati con @ServerTimestamp
come segue: se l'attributo è nil
quando lo memorizzi (ad esempio utilizzando addDocument()
), Cloud Firestore completerà il campo con il timestamp del server corrente al momento della scrittura nel database. Se il campo non è nil
quando chiami addDocument()
o updateData()
, Cloud Firestore lascerà il valore dell'attributo invariato. In questo modo, è facile implementare campi come
createdAt
e lastUpdatedAt
.
Geopoint
Le geolocalizzazioni sono onnipresenti nelle nostre app. Molte funzionalità interessanti diventano possibili grazie allo stoccaggio. Ad esempio, potrebbe essere utile memorizzare una posizione per un'attività in modo che l'app possa ricordarti un'attività quando raggiungi una destinazione.
Cloud Firestore ha un tipo di dati integrato, GeoPoint
, che può memorizzare la longitudine e la latitudine di qualsiasi località. Per mappare le posizioni da/a un
documento Cloud Firestore, possiamo utilizzare il tipo GeoPoint
:
struct Office: Codable {
@DocumentID var id: String?
var name: String
var location: GeoPoint
}
Il tipo corrispondente in Swift è CLLocationCoordinate2D
e possiamo eseguire la mappatura tra questi due tipi con la seguente operazione:
CLLocationCoordinate2D(latitude: office.location.latitude,
longitude: office.location.longitude)
Per scoprire di più su come eseguire query sui documenti in base alla posizione fisica, consulta questa guida alla soluzione.
Enum
Gli enum sono probabilmente una delle funzionalità del linguaggio più sottovalutate in Swift;
c'è molto di più di quanto sembri. Un caso d'uso comune per gli enum è rappresentare gli stati discreti di qualcosa. Ad esempio, potremmo scrivere un'app per la gestione degli articoli. Per monitorare lo stato di un articolo, potremmo utilizzare un valore di enumerazione Status
:
enum Status: String, Codable {
case draft
case inReview
case approved
case published
}
Cloud Firestore non supporta gli enum in modo nativo (ovvero non può applicare l'insieme di valori), ma possiamo comunque sfruttare il fatto che gli enum possono essere digitati e scegliere un tipo codificabile. In questo esempio abbiamo scelto String
, il che significa che tutti i valori dell'enum verranno mappati a/da stringa quando vengono archiviati in un documento Cloud Firestore.
Inoltre, poiché Swift supporta i valori non elaborati personalizzati, possiamo anche personalizzare i valori
che fanno riferimento a quale caso enum. Ad esempio, se decidiamo di memorizzare la richiesta Status.inReview
come "in corso di revisione", possiamo semplicemente aggiornare l'enum precedente come segue:
enum Status: String, Codable {
case draft
case inReview = "in review"
case approved
case published
}
Personalizzazione della mappatura
A volte, i nomi degli attributi dei documenti Cloud Firestore che vogliamo mappare non corrispondono ai nomi delle proprietà nel nostro modello di dati in Swift. Ad esempio, uno dei nostri colleghi potrebbe essere uno sviluppatore Python e aver deciso di scegliere snake_case per tutti i nomi degli attributi.
Non preoccuparti: Codable è la soluzione che fa per te.
Per casi come questi, possiamo utilizzare CodingKeys
. Si tratta di un enum che possiamo
aggiungere a una struct codificabile per specificare in che modo verranno mappati determinati attributi.
Considera questo documento:
Per mappare questo documento a una struttura con una proprietà name di tipo String
, dobbiamo aggiungere un enum CodingKeys
alla struttura ProgrammingLanguage
e specificare il nome dell'attributo nel documento:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
enum CodingKeys: String, CodingKey {
case id
case name = "language_name"
case year
}
}
Per impostazione predefinita, l'API Codable utilizza i nomi delle proprietà dei nostri tipi Swift per determinare i nomi degli attributi nei documenti Cloud Firestore che stiamo tentando di mappare. Pertanto, se i nomi degli attributi corrispondono, non è necessario aggiungere
CodingKeys
ai nostri tipi codificabili. Tuttavia, una volta utilizzato CodingKeys
per un tipo specifico, dobbiamo aggiungere tutti i nomi delle proprietà che vogliamo mappare.
Nello snippet di codice riportato sopra abbiamo definito una proprietà id
che potremmo voler utilizzare come identificatore in una vista List
di SwiftUI. Se non lo specifichiamo in
CodingKeys
, non verrà mappato durante il recupero dei dati e diventerà nil
.
Di conseguenza, la visualizzazione List
verrà compilata con il primo documento.
Qualsiasi proprietà non elencata come caso nell'enum CodingKeys
corrispondente verrà ignorata durante la procedura di mappatura. Questo può essere utile se vogliamo escludere specificamente alcune proprietà dalla mappatura.
Ad esempio, se vogliamo escludere la proprietà reasonWhyILoveThis
dalla mappatura, dobbiamo solo rimuoverla dall'enum CodingKeys
:
struct ProgrammingLanguage: Identifiable, Codable {
@DocumentID var id: String?
var name: String
var year: Date
var reasonWhyILoveThis: String = ""
enum CodingKeys: String, CodingKey {
case id
case name = "language_name"
case year
}
}
A volte potremmo voler scrivere di nuovo un attributo vuoto nel
documento Cloud Firestore. Swift ha il concetto di valori facoltativi per indicare l'assenza di un valore e Cloud Firestore supporta anche i valori null
.
Tuttavia, il comportamento predefinito per la codifica degli elementi facoltativi che hanno un valore nil
è semplicemente ometterli. @ExplicitNull
ci offre un certo controllo sulla modalità di gestione degli optional di Swift durante la loro codifica: contrassegnando una proprietà facoltativa come @ExplicitNull
, possiamo indicare a Cloud Firestore di scrivere questa proprietà nel documento con un valore null se contiene un valore nil
.
Utilizzo di un codificatore e decodificatore personalizzati per la mappatura dei colori
Come ultimo argomento della nostra copertura della mappatura dei dati con Codable, introduciamo gli codificatori e decodificatori personalizzati. Questa sezione non tratta un tipo di dato Cloud Firestore nativo, ma gli encoder e i decodificatori personalizzati sono molto utili nelle app Cloud Firestore.
"Come faccio a mappare i colori" è una delle domande più frequenti degli sviluppatori, non solo per Cloud Firestore, ma anche per la mappatura tra Swift e JSON. Esistono molte soluzioni, ma la maggior parte si concentra su JSON e quasi tutte mappano i colori come un dizionario nidificato composto dai relativi componenti RGB.
Sembra che debba esserci una soluzione migliore e più semplice. Perché non utilizziamo i colori web (o, per essere più specifici, la notazione dei colori esadecimali CSS)? Perché sono facili da usare (essenzialmente solo una stringa) e supportano persino la trasparenza.
Per poter mappare un Color
Swift al relativo valore esadecimale, dobbiamo creare un'estensione Swift che aggiunga Codable a Color
.
extension Color {
init(hex: String) {
let rgba = hex.toRGBA()
self.init(.sRGB,
red: Double(rgba.r),
green: Double(rgba.g),
blue: Double(rgba.b),
opacity: Double(rgba.alpha))
}
//... (code for translating between hex and RGBA omitted for brevity)
}
extension Color: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hex = try container.decode(String.self)
self.init(hex: hex)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(toHex)
}
}
Utilizzando decoder.singleValueContainer()
, possiamo decodificare un String
nel suo equivalente Color
, senza dover nidificare i componenti RGBA. Inoltre, puoi utilizzare questi valori nell'interfaccia utente web della tua app senza doverli prima convertire.
In questo modo, possiamo aggiornare il codice per la mappatura dei tag, facilitando la gestione diretta dei colori dei tag anziché doverli mappare manualmente nel codice UI della nostra app:
struct Tag: Codable, Hashable {
var title: String
var color: Color
}
struct BookWithTags: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var tags: [Tag]
}
Gestione degli errori
Negli snippet di codice precedenti abbiamo intenzionalmente ridotto al minimo la gestione degli errori, ma in un'app di produzione è importante gestire in modo corretto eventuali errori.
Ecco uno snippet di codice che mostra come gestire eventuali situazioni di errore che potresti riscontrare:
class MappingSimpleTypesViewModel: ObservableObject {
@Published var book: Book = .empty
@Published var errorMessage: String?
private var db = Firestore.firestore()
func fetchAndMap() {
fetchBook(documentId: "hitchhiker")
}
func fetchAndMapNonExisting() {
fetchBook(documentId: "does-not-exist")
}
func fetchAndTryMappingInvalidData() {
fetchBook(documentId: "invalid-data")
}
private func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument(as: Book.self) { result in
switch result {
case .success(let book):
// A Book value was successfully initialized from the DocumentSnapshot.
self.book = book
self.errorMessage = nil
case .failure(let error):
// A Book value could not be initialized from the DocumentSnapshot.
switch error {
case DecodingError.typeMismatch(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self.errorMessage = "\(error.localizedDescription): \(key)"
default:
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
}
}
Gestione degli errori negli aggiornamenti in tempo reale
Lo snippet di codice precedente mostra come gestire gli errori durante il recupero di un singolo documento. Oltre a recuperare i dati una volta, Cloud Firestore supporta anche l'invio di aggiornamenti alla tua app man mano che si verificano, utilizzando i cosiddetti ascoltatori di snapshot: possiamo registrare un ascoltatore di snapshot in una raccolta (o query) e Cloud Firestore chiamerà il nostro ascoltatore ogni volta che c'è un aggiornamento.
Ecco uno snippet di codice che mostra come registrare un ascoltatore di snapshot, mappare i dati utilizzando Codable e gestire eventuali errori che potrebbero verificarsi. Inoltre, mostra come aggiungere un nuovo documento alla raccolta. Come vedrai, non è necessario aggiornare personalmente l'array locale contenente i documenti mappati, perché è il codice nell'ascoltatore di istantanee a occuparsene.
class MappingColorsViewModel: ObservableObject {
@Published var colorEntries = [ColorEntry]()
@Published var newColor = ColorEntry.empty
@Published var errorMessage: String?
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("colors")
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.errorMessage = "No documents in 'colors' collection"
return
}
self?.colorEntries = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: ColorEntry.self) }
switch result {
case .success(let colorEntry):
if let colorEntry = colorEntry {
// A ColorEntry value was successfully initialized from the DocumentSnapshot.
self?.errorMessage = nil
return colorEntry
}
else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
self?.errorMessage = "Document doesn't exist."
return nil
}
case .failure(let error):
// A ColorEntry value could not be initialized from the DocumentSnapshot.
switch error {
case DecodingError.typeMismatch(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self?.errorMessage = "\(error.localizedDescription): \(key)"
default:
self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
return nil
}
}
}
}
}
func addColorEntry() {
let collectionRef = db.collection("colors")
do {
let newDocReference = try collectionRef.addDocument(from: newColor)
print("ColorEntry stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
}
Tutti gli snippet di codice utilizzati in questo post fanno parte di un'applicazione di esempio che puoi scaricare da questo repository GitHub.
Vai avanti e usa Codable.
L'API Codable di Swift offre un modo efficace e flessibile per mappare i dati da formati serializzati verso e verso il modello di dati delle applicazioni. In questa guida, hai visto quanto è facile da utilizzare nelle app che utilizzano Cloud Firestore come datastore.
Partendo da un esempio di base con tipi di dati semplici, abbiamo progressivamente aumentato la complessità del modello di dati, il tutto potendo fare affidamento su l'implementazione di Codable e Firebase per eseguire la mappatura per nostro conto.
Per maggiori dettagli su Codable, ti consiglio le seguenti risorse:
- John Sundell ha scritto un bel articolo sulle nozioni di base di Codable.
- Se preferisci i libri, dai un'occhiata alla guida di Mattt alla scuola di volo per Swift Codable.
- Infine, Donny Wals ha un'intera serie su Codable.
Abbiamo fatto del nostro meglio per compilare una guida completa per la mappatura dei documenti Cloud Firestore, ma non è esaustiva e potresti utilizzare altre strategie per mappare i tuoi tipi. Utilizzando il pulsante Invia feedback di seguito, facci sapere quali strategie utilizzi per mappare altri tipi di Cloud Firestore dati o per rappresentarli in Swift.
Non c'è davvero alcun motivo per non utilizzare il supporto Codable di Cloud Firestore.