La API de Codable de Swift, que se presentó en Swift 4, nos permite aprovechar la potencia del compilador para facilitar la asignación de datos de formatos serializados a tipos de Swift.
Es posible que hayas usado Codable para asignar datos de una API web al modelo de datos de tu app (y viceversa), pero es mucho más flexible.
En esta guía, veremos cómo se puede usar Codable para asignar datos de Cloud Firestore a tipos de Swift y viceversa.
Cuando se recupera un documento de Cloud Firestore, tu app recibirá un diccionario de pares clave-valor (o un array de diccionarios, si usas una de las operaciones que muestran varios documentos).
Ahora, puedes continuar usando directamente los diccionarios en Swift, y ofrecen una gran flexibilidad que podría ser justo lo que requiere tu caso de uso. Sin embargo, este enfoque no cuenta con seguridad de tipos y, además, es fácil ingresar errores difíciles de encontrar cuando escribes errores ortográficos en los nombres de atributos o si olvidas asignar el nuevo atributo que agregó tu equipo cuando lanzó una emocionante función nueva la semana pasada.
En el pasado, muchos desarrolladores solucionaron estas deficiencias implementando de una capa de asignación simple que les permitió asignar diccionarios a los tipos de Swift. Sin embargo, la mayoría de estas implementaciones se basan en especificar de forma manual la asignación entre documentos de Cloud Firestore y los tipos correspondientes del modelo de datos de tu app.
La compatibilidad de Cloud Firestore con la API de Codable de Swift facilita mucho más este proceso:
- Ya no tendrás que implementar manualmente ningún código de asignación.
- Es fácil definir cómo asignar atributos con nombres diferentes.
- Tiene compatibilidad integrada con muchos de los tipos de Swift.
- Además, es fácil agregar compatibilidad con la asignación de tipos personalizados.
- Lo mejor de todo es que, para los modelos de datos simples, no tendrás que escribir código de asignación.
Asigna datos
Cloud Firestore almacena datos en documentos que asignan claves a valores. Para recuperar datos de un documento individual, podemos llamar a DocumentSnapshot.data()
, que muestra un diccionario que asigna los nombres de campo a Any
:
func data() -> [String : Any]?
.
Esto significa que podemos usar la sintaxis de subíndice de Swift para acceder a cada campo individual.
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)
}
}
}
}
Si bien puede parecer sencillo y fácil de implementar, este código es frágil, difícil de mantener y propenso a errores.
Como puedes ver, estamos haciendo suposiciones sobre los tipos de datos de los campos del documento. Estos pueden o no ser correctos.
Recuerda que, como no hay esquema, puedes agregar fácilmente un documento nuevo
a la colección y elegir un tipo diferente para un campo. Por accidente,
puedes elegir una cadena para el campo numberOfPages
, lo que generaría
un problema de asignación difícil de encontrar. Además, deberás actualizar el código de asignación
cada vez que se agregue un campo nuevo, lo que es bastante engorroso.
Y no olvidemos que, de esta forma, no estamos aprovechando el sistema estricto de tipos de Swift,
que conoce exactamente el tipo correcto de cada una de las propiedades de
Book
.
¿Qué significa Codable?
Según la documentación de Apple, Codable es un “tipo que puede convertirse en una representación externa y dejar de ser una”. De hecho, Codable es un alias de tipo para los protocolos Encodable y Decodable. Cuando un tipo de Swift se ajusta a este protocolo, el compilador sintetiza el código necesario para codificar o decodificar una instancia de este tipo a partir de un formato serializado, como JSON.
Un tipo simple para almacenar datos sobre un libro podría verse así:
struct Book: Codable {
var title: String
var numberOfPages: Int
var author: String
}
Como puedes ver, establecer el tipo en Codable es mínimamente invasivo. Solo tuvimos que agregar la conformidad con el protocolo. No se requirieron otros cambios.
Con este cambio implementado, ahora podemos codificar fácilmente un libro en un objeto 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 decodificación de un objeto JSON en una instancia Book
funciona de la siguiente manera:
let decoder = JSONDecoder()
let data = /* fetch data from the network */
let decodedBook = try decoder.decode(Book.self, from: data)
Asigna desde y hacia tipos simples en documentos Cloud Firestore
con Codable
Cloud Firestore admite un amplio conjunto de tipos de datos, que van desde simples cadenas a mapas anidados. La mayoría de estos corresponden directamente a los tipos integrados de Swift. Veamos cómo asignar algunos tipos de datos simples antes de profundizar en los más complejos.
Para asignar documentos Cloud Firestore a tipos de Swift, sigue estos pasos:
- Asegúrate de que agregaste el framework
FirebaseFirestore
a tu proyecto. Puedes usar Swift Package Manager o CocoaPods para hacerlo. - Importa
FirebaseFirestore
a tu archivo de Swift. - Adapta tu tipo a
Codable
. - Opcional: Si quieres usar el tipo en una vista
List
, agrega una propiedadid
a tu tipo y usa@DocumentID
para indicarle a Cloud Firestore que lo asigne al ID del documento. Analizaremos este tema con más detalle a continuación. - Usa
documentReference.data(as: )
para asignar una referencia de documento a un tipo Swift. - Usa
documentReference.setData(from: )
para asignar datos de tipos de Swift a un documento Cloud Firestore. - Opcional pero muy recomendable: Implementa el manejo adecuado de errores.
Actualicemos nuestro tipo Book
según corresponda:
struct Book: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
Como este tipo ya se ajustaba al protocolo Codable, solo tuvimos que agregar la propiedad id
y
anotarla con el wrapper de la propiedad @DocumentID
.
Tomando el fragmento de código anterior para recuperar y asignar un documento, podemos reemplazar todo el código de asignación manual por una sola línea:
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)
}
}
}
}
}
Puedes escribirlo de forma aún más concisa especificando el tipo de documento
cuando llamas a getDocument(as:)
. Este procedimiento realizará la asignación por ti y
mostrará un tipo Result
que contiene el documento asignado o un error en caso
de que la decodificación falle:
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)"
}
}
}
Actualizar un documento existente es tan simple como llamar a
documentReference.setData(from: )
. Este
es el código para guardar una instancia de Book
(incluye medidas básicas de manejo de errores):
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)
}
}
}
Cuando se agrega un documento nuevo, Cloud Firestore se encarga automáticamente de asignarle un ID de documento nuevo. Esto incluso funciona cuando la app está sin conexión.
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)
}
}
Además de asignar tipos de datos simples, Cloud Firestore admite otros tipos de datos, algunos de los cuales son tipos estructurados que puedes usar para crear objetos anidados dentro de un documento.
Tipos personalizados anidados
La mayoría de los atributos que queremos asignar en nuestros documentos son valores simples, como el título del libro o el nombre del autor, pero ¿qué ocurre cuando necesitamos almacenar un objeto más complejo? Por ejemplo, es posible que queramos almacenar las URLs de la portada del libro en diferentes resoluciones.
La forma más fácil de hacerlo en Cloud Firestore es usar un mapa:
Cuando se escribe la struct de Swift correspondiente, podemos aprovechar la compatibilidad de Cloud Firestore con URLs. Cuando se almacene un campo que contenga una URL, se convertirá en una cadena y 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?
}
Observa cómo definimos un struct, CoverImages
, para el mapa de portada en la Cloud Firestore documento. Si marcas la propiedad de portada en
BookWithCoverImages
como opcional, podemos controlar el hecho de que es posible que algunos
documentos no contengan un atributo de portada.
Si te interesa saber por qué no hay un fragmento de código para recuperar o actualizar datos, te alegrará saber que no necesitas ajustar el código para leer ni escribir desde Cloud Firestore: esto funciona con el código escrito en la sección inicial.
Arrays
A veces, queremos almacenar una colección de valores en un documento. Los géneros de un libro son un buen ejemplo: un libro como Guía del autoestopista galáctico puede aparecer en varias categorías, en este caso, “Ciencia ficción” y “Comedia”:
En Cloud Firestore, podemos modelar esto con un array de valores. Esto es compatible con cualquier tipo codificado (como String
, Int
, etcétera). A continuación, se muestra cómo agregar un array de géneros a nuestro modelo Book
:
public struct BookWithGenre: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var genres: [String]
}
Como esto funciona para cualquier tipo codificado, también podemos usar tipos personalizados. Imagina que queremos almacenar una lista de etiquetas para cada libro. Junto con el nombre de la etiqueta, nos gustaría almacenar el color de la etiqueta también:
Para almacenar etiquetas de esta manera, lo único que debemos hacer es implementar una struct Tag
para
representar una etiqueta y hacer que se pueda codificar:
struct Tag: Codable, Hashable {
var title: String
var color: String
}
Y, así, podemos almacenar un array de Tags
en nuestros documentos de Book
.
struct BookWithTags: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var tags: [Tag]
}
Información breve sobre la asignación de los IDs de documento
Antes de comenzar a asignar más tipos, hablemos de la asignación de los IDs de documento por un momento.
Usamos el wrapper de propiedad @DocumentID
en algunos de los ejemplos anteriores para asignar los IDs de nuestros documentos de Cloud Firestore a la propiedad id
de nuestros tipos de Swift. Este paso es importante por varios motivos:
- Nos ayuda a saber qué documento actualizar en caso de que el usuario haga cambios locales.
- El
List
de SwiftUI requiere que sus elementos seanIdentifiable
para evitar que aparezcan cuando se insertan.
Vale la pena señalar que el codificador de Cloud Firestore no codificará un atributo marcado como @DocumentID
cuando se vuelva a escribir el documento, ya que el ID del documento no es un atributo del documento en sí, por lo que escribirlo sería un error.
Cuando trabajas con tipos anidados (como el array de etiquetas en el Book
de un ejemplo anterior de esta guía), no es necesario agregar una propiedad
@DocumentID
: las propiedades anidadas son parte del documento de Cloud Firestore y no constituyen un documento independiente. Por lo tanto, no se necesita un ID de documento.
Fechas y horas
Cloud Firestore tiene un tipo de datos integrado para administrar fechas y horas, y gracias a la compatibilidad de Cloud Firestore con Codable, es fácil de usar.
Veamos este documento que representa a la madre de todos los lenguajes de programación, Ada, inventado en 1843:
Un tipo de Swift para asignar este documento podría tener el siguiente aspecto:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
}
No podemos salir de esta sección sobre fechas y horas sin tener una conversación
sobre @ServerTimestamp
. Este wrapper de propiedad es una potencia a la hora de
tratar con marcas de tiempo en tu app.
En cualquier sistema distribuido, es posible que los relojes de los sistemas individuales no estén completamente sincronizados todo el tiempo. Se podría pensar que esto no es muy importante, pero imagina las implicaciones de que un reloj esté un poco desincronizado para un sistema de comercio de valores, incluso una desviación de milisegundos podría dar como resultado una diferencia de millones de dólares al realizar un intercambio.
Cloud Firestore controla los atributos marcados con @ServerTimestamp
de la siguiente manera: si el atributo es nil
cuando lo almacenas (con addDocument()
, por ejemplo), Cloud Firestore completará el campo con la marca de tiempo actual del servidor en el momento de la escritura en la base de datos. Si el campo no es nil
cuando llames a addDocument()
o updateData()
, Cloud Firestore dejará el valor del atributo intacto. De esta manera, es fácil implementar campos como createdAt
y lastUpdatedAt
.
Puntos geográficos
Las ubicaciones geográficas son omnipresentes en nuestras apps. Almacenarlas desbloquea una gran cantidad de funciones emocionantes. Por ejemplo, podría ser útil almacenar la ubicación de una tarea para que tu app pueda recordártela cuando llegues a un lugar de destino.
Cloud Firestore tiene un tipo de datos integrado, GeoPoint
, que puede almacenar la longitud y latitud de cualquier ubicación. Para asignar ubicaciones desde o hacia un documento Cloud Firestore, podemos usar el tipo GeoPoint
:
struct Office: Codable {
@DocumentID var id: String?
var name: String
var location: GeoPoint
}
El tipo correspondiente en Swift es CLLocationCoordinate2D
y podemos asignar
entre esos dos tipos con la siguiente operación:
CLLocationCoordinate2D(latitude: office.location.latitude,
longitude: office.location.longitude)
Si necesitas más información para consultar documentos por ubicación física, consulta esta guía de solución.
Enums
Es posible que las enums sean una de las funciones de lenguaje más subestimadas en Swift.
Son mucho más interesantes de lo que parece. Un caso de uso común para las enums es
modelar los estados discretos de un elemento. Por ejemplo, podríamos escribir una app
para administrar artículos. Para hacer un seguimiento del estado de un artículo, te recomendamos que uses
una enum Status
:
enum Status: String, Codable {
case draft
case inReview
case approved
case published
}
Cloud Firestore no admite enums de forma nativa (es decir, no puede aplicar el conjunto de valores), pero podemos seguir usando el hecho de que las enums pueden tener asignado un tipo y elegir uno que pueda codificar. En este ejemplo, elegimos String
, lo que significa que todos los valores de enum se asignarán desde y hacia la cadena cuando se almacenen en un documento de Cloud Firestore.
Además, como Swift admite valores sin procesar personalizados, podemos personalizar qué valores
hacen referencia a cada caso de enum. Por ejemplo, si decidimos almacenar el caso
Status.inReview
como “en revisión”, podríamos actualizar la enum anterior de la siguiente
manera:
enum Status: String, Codable {
case draft
case inReview = "in review"
case approved
case published
}
Personaliza la asignación
A veces, los nombres de los atributos de los documentos de Cloud Firestore que queremos asignar no coinciden con los nombres de las propiedades en nuestro modelo de datos en Swift. Por ejemplo, uno de nuestros compañeros de trabajo puede ser desarrollador de Python y decidió usar minúsculas_con_guiones_bajos para todos los nombres de sus atributos.
No te preocupes: ¡Codable nos tiene cubierto!
Para casos como este, podemos usar CodingKeys
. Esta es una enum que podemos
agregar a una struct codificable para especificar cómo se asignarán ciertos atributos.
Considera este documento:
Para asignar este documento a una struct que tiene una propiedad de nombre de tipo String
,
debemos agregar una enum CodingKeys
a la estructura ProgrammingLanguage
y especificar
el nombre del atributo. en el 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
}
}
Según la configuración predeterminada, la API de Codable usará los nombres de las propiedades de nuestros tipos de Swift para determinar los nombres de los atributos en los documentos de Cloud Firestore que estamos intentando asignar. Siempre que los nombres de los atributos coincidan, no es necesario agregar CodingKeys
a nuestros tipos codificables. Sin embargo, una vez que usamos CodingKeys
para un tipo específico, debemos agregar todos los nombres de propiedades que queremos asignar.
En el fragmento de código anterior, definimos una propiedad id
que quizás desees
usar como identificador en una vista List
de SwiftUI. Si no se especificara en
CodingKeys
, no se asignaría cuando se recuperan datos y, por lo tanto, se convertiría en nil
.
De esta manera, la vista List
se completará con el primer documento.
Durante el proceso de asignación, se pasarán por alto las propiedades que no figuren como
casos en la enum CodingKeys
respectiva. En realidad, esto puede ser conveniente si
queremos excluir de manera específica algunas de las propiedades de la asignación.
Por ejemplo, si queremos excluir la propiedad reasonWhyILoveThis
de la asignación,
lo único que debemos hacer es quitarla de la 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
}
}
En ocasiones, es posible que queramos escribir un atributo vacío en el documento Cloud Firestore. Swift tiene la noción de opcionales para denotar
la ausencia de un valor, y Cloud Firestore también admite valores null
.
Sin embargo, el comportamiento predeterminado para la codificación opcional que tiene un valor nil
es omitirlos. @ExplicitNull
nos da cierto control sobre el manejo de las opciones de Swift cuando las codificas. Marcando una propiedad opcional como @ExplicitNull
, podemos indicarle a Cloud Firestore que escriba esta propiedad en el documento con un valor nulo si contiene un valor de nil
.
Usa un codificador y decodificador personalizado para asignar colores
Como último tema de nuestra cobertura de la asignación de datos con Codable, presentaremos los codificadores y decodificadores personalizados. En esta sección, no se abarca un tipo de datos nativo de Cloud Firestore, pero los codificadores y decodificadores personalizados son muy útiles en tus apps de Cloud Firestore.
“Cómo puedo asignar colores” es una de las preguntas más frecuentes de los desarrolladores, no solo para Cloud Firestore, sino también para las asignaciones entre Swift y JSON. Existen muchas soluciones, pero la mayoría de ellas se enfocan en JSON y casi todas ellas asignan colores como un diccionario anidado compuesto por sus componentes RGB.
Parece que debería haber una solución mejor y más simple. ¿Por qué no usamos colores web (o, para ser más específicos, notación hexadecimal de color CSS)? Son fáciles de usar (básicamente solo una cadena) y admiten la transparencia.
Para poder asignar un Color
de Swift a su valor hexadecimal, debemos crear una extensión
de Swift que agregue 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)
}
}
Cuando se usa decoder.singleValueContainer()
, podemos decodificar un String
a su
equivalente Color
, sin tener que anidar los componentes RGBA. Además, puedes
usar estos valores en la IU web de tu app sin tener que convertirlos
primero.
De esta manera, podemos actualizar el código para asignar etiquetas, lo que facilita el control directo de los colores de las etiquetas en lugar de tener que asignarlos manualmente en el código de IU de nuestra 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]
}
Maneja los errores
En los fragmentos de código anteriores, mantuvimos intencionalmente el manejo de errores como mínimo, pero en una app de producción, debes asegurarte de manejar cualquier error.
Este es un fragmento de código en el que se muestra cómo manejar cualquier situación de error con la que te encuentres:
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)"
}
}
}
}
}
Maneja errores en las actualizaciones en tiempo real
El fragmento de código anterior muestra cómo manejar los errores cuando se recupera un solo documento. Además de recuperar datos una vez, Cloud Firestore también admite la entrega de actualizaciones de tu app a medida que ocurren mediante los objetos de escucha de instantáneas: podemos registrar un objeto de escucha de instantáneas en una colección (o consulta) y Cloud Firestore llamará a nuestro agente de escucha cuando haya una actualización.
A continuación, se muestra un fragmento de código que muestra cómo registrar un objeto de escucha de instantáneas, asignar datos mediante Codable y manejar cualquier error que pueda ocurrir. También se muestra cómo agregar un documento nuevo a la colección. Como verás, no es necesario actualizar el array local que contenga los documentos asignados, ya que el código del objeto de escucha de instantáneas se encarga de esto.
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)
}
}
}
Todos los fragmentos de código que se usan en esta publicación forman parte de una aplicación de ejemplo que puedes descargar desde este repositorio de GitHub.
Comienza a usar Codable
La API de Codable de Swift proporciona una manera potente y flexible de asignar datos de los formatos serializados hacia y desde el modelo de datos de tus aplicaciones. En esta guía, viste lo fácil que es usarla en las apps que utilizan Cloud Firestore como almacén de datos.
A partir de un ejemplo básico con tipos de datos simples, aumentamos progresivamente la complejidad del modelo de datos y, al mismo tiempo, pudimos confiar en que la implementación de Codable y Firebase realizaría la asignación por nosotros.
Si buscas más detalles sobre Codable, te recomendamos los siguientes recursos:
- John Sundell tiene un buen artículo sobre los conceptos básicos de Codable.
- Si prefieres los libros, consulta la Flight School Guide to Swift Codable de Mattt.
- Por último, Donny Wals tiene una serie completa sobre Codable.
Si bien hicimos todo lo posible para crear una guía completa sobre cómo asignar documentos de Cloud Firestore, no es exhaustiva y es posible que uses otras estrategias para asignar tus tipos. Usa el botón Enviar comentarios que aparece a continuación y cuéntanos qué estrategias usas para asignar otros tipos de datos de Cloud Firestore o representar datos en Swift.
No hay razón para no usar la compatibilidad con Codable de Cloud Firestore.