Contenido del curso
Arquitectura
Gestión de Datos en SwiftData
- 8

Uso de SWIFTDA para Persistencia de Datos en iOS 17
04:31 min - 9

Cómo grabar datos con SwiftData
10:55 min - 10

Implementación de Queries con Predicados en Swift Data
11:08 min - 11

Corrección automática y actualización de registros en SWIFTDA
06:59 min - 12

Eliminar registros y calcular totales en SwiftData
04:20 min
Gestión de Datos en Realm
¿Swift Data o Realm?
Inyectando DatabaseServiceProtocol en ViewModels
Resumen
Refactorizar tus ViewModels para que dejen de depender de datos mockeados y empiecen a usar un protocolo de base de datos es el paso que separa una app de práctica de una app lista para producción. Aquí aprenderás a aplicar inyección de dependencias con DatabaseServiceProtocol en SwiftUI para que tu código funcione igual con datos mock, SwiftData o Realm.
Por qué reemplazar el mock helper por un DatabaseServiceProtocol
Cuando empiezas un proyecto, es común llamar directamente a un helper como MockRecordsHelper desde el HomeViewModel. Funciona, pero te amarra a una sola fuente de datos. Y aquí viene lo interesante: si mañana quieres cambiar a SwiftData, tendrías que tocar el ViewModel entero.
La solución es declarar una variable databaseService del tipo del protocolo y recibirla como parámetro en el init. Así el ViewModel no sabe ni le importa quién implementa el protocolo, solo lo usa.
¿Qué es la inyección de dependencias en SwiftUI? Es pasar un objeto que cumple un protocolo como parámetro del
init, en lugar de crearlo dentro de la clase. Eso te permite intercambiar implementaciones (mock, real, de prueba) sin modificar el ViewModel [02:10].
Cómo refactorizar el HomeViewModel paso a paso
El HomeViewModel tenía dos todos pendientes: getRecords y getTotals. Ambos usaban data mockeada directamente. La estrategia es la misma para los dos: envolver la llamada al servicio en un Task porque las funciones del database service son asíncronas y necesitas usar await dentro de funciones que no son async [01:30].
Cómo traer los registros con fetchRecords
Dentro del Task, llamas a databaseService.fetchRecords() con await para obtener los records. Después, en el MainActor, cambias loading de true a false para que la vista se recargue con los datos reales que devolvió el servicio.
Cómo calcular totales de ingresos y egresos
La función getTotals sigue la misma lógica: un Task, un await al servicio que devuelve una dupla con totalIncome y totalOutcome, y luego en el MainActor se actualiza loadingTotals a false. Con eso la vista refresca y muestra los totales calculados.
Cómo aplicar el mismo patrón en FormRecordViewModel y RecordDetailViewModel
El FormRecordViewModel tenía un detalle importante que corregir antes de seguir: al actualizar un registro no debes generar un nuevo UUID, sino conservar el ID del registro original [03:45]. Generar un nuevo UUID rompe la trazabilidad y causa problemas al sincronizar con la base de datos.
Después de ese fix, el patrón se repite:
- Declara
databaseService: DatabaseServiceProtocolcomo propiedad. - Recibe el protocolo en el
inite inicialízalo. - Reemplaza las funciones mockeadas por llamadas reales dentro de un
Task. - Llama a
saveNewRecordoupdateRecordsegún corresponda. - Si la operación es exitosa, apaga el loading; si falla, muestra el error.
¿Cómo manejo errores cuando falla saveNewRecord? El instructor lo deja como tarea: en el
elsedel resultado, propaga el error al ViewModel y desde ahí actualiza una propiedad@Publishedque la vista observe para mostrar una alerta [05:20].
El RecordDetailViewModel repite el patrón con la función deleteRecord. Recibe el DatabaseServiceProtocol por inyección, envuelve la llamada en un Task, y al terminar dispara el completion para que la vista navegue o se actualice.
Cómo propagar el DatabaseService desde la app principal hasta cada vista
Al compilar verás errores en el archivo principal de la app porque el HomeViewModel ya no tiene un inicializador vacío. La solución es crear el servicio una sola vez en el entry point de la aplicación e inyectarlo hacia abajo.
swift let databaseService: DatabaseServiceProtocol = MockDatabaseService() HomeView(viewModel: HomeViewModel(databaseService: databaseService))
Desde el HomeView, cuando navegas a FormRecord o a RecordDetail, pasas selfVM.databaseService para que esos ViewModels hereden la misma instancia. Esa cadena es la clave: el servicio se crea al inicio, se inyecta en el Home y desde ahí fluye a cada vista que lo necesite [08:15].
Qué pasar en los previews de SwiftUI
Los #Preview también fallan al compilar porque exigen el protocolo. Aquí es donde el MockDatabaseService brilla: lo pasas en cada preview del HomeView, FormRecord y RecordDetail para que Xcode pueda renderizar las vistas sin conectarse a una base real.
HomeView(viewModel: HomeViewModel(databaseService: MockDatabaseService())).FormRecordView(viewModel: FormRecordViewModel(databaseService: MockDatabaseService())).RecordDetailView(viewModel: RecordDetailViewModel(databaseService: MockDatabaseService())).
Qué validar al correr la app después del refactor
Una vez que Command + B te muestre build success, corre la app en el simulador y revisa el flujo completo: filtros del Home, entrada al detalle, edición de un record y guardado. Si todo responde igual que antes, significa que la arquitectura quedó lista para conectarse a una base de datos real como SwiftData o Realm sin tocar los ViewModels.
¿Qué database service vas a implementar primero en tu proyecto? Cuéntame en los comentarios.