El sustituto de Core Data: Guía práctica de SwiftData en proyectos reales

Durante más de una década, Core Data ha sido el titán indiscutible de la persistencia de datos en el ecosistema de Apple. Sin embargo, su curva de aprendizaje empinada, su verbosidad y una API diseñada en la era de Objective-C hacían que muchos desarrolladores temieran enfrentarse a él.

Con la llegada de iOS 17 y Swift 5.9, Apple presentó SwiftData: el sucesor espiritual y tecnológico de Core Data. Diseñado desde cero para el Swift moderno y con una integración nativa en SwiftUI, SwiftData promete eliminar el código repetitivo (boilerplate) y hacer que la persistencia sea algo natural.

En este artículo, analizaremos qué es SwiftData, cómo estructurar un proyecto real con él y las mejores prácticas para implementarlo con éxito.


¿Qué es SwiftData y por qué jubila a Core Data?

SwiftData no es un motor de base de datos nuevo; de hecho, por debajo sigue utilizando el robusto motor de Core Data y SQLite. La verdadera revolución radica en cómo interactuamos con él.

A diferencia de Core Data, donde debías configurar un archivo gráfico .xcdatamodeld, generar clases pesadas y lidiar con contextos de hilos complejos, SwiftData aprovecha el poder de las Swift Macros para hacer todo esto directamente en tu código Swift.

Ventajas clave de SwiftData:

  • Declarativo y nativo de Swift: Olvídate de los editores visuales de modelos. Tus clases de Swift son tus modelos de base de datos.
  • Integración total con SwiftUI: Actualiza la interfaz de usuario automáticamente cuando los datos cambian mediante el uso de @Query.
  • Seguridad de tipos y concurrencia: Aprovecha los nuevos Actors de Swift para garantizar que las operaciones en segundo plano sean seguras y no bloqueen el hilo principal.

Los tres pilares de SwiftData

Para dominar SwiftData en proyectos reales, debes entender tres conceptos fundamentales:

  1. @Model (El Esquema): Es una macro que transforma cualquier clase estándar de Swift en un modelo persistente gestionado por SwiftData.
  2. ModelContainer (El Almacenamiento): Es el backend que gestiona la base de datos (en memoria o en el disco). Define qué esquemas se van a persistir.
  3. ModelContext (La Memoria de Trabajo): Es el intermediario. Se encarga de rastrear los cambios, insertar objetos, eliminarlos y guardarlos en el ModelContainer.

Guía práctica: Implementando SwiftData en un proyecto real

Imaginemos que estamos construyendo una aplicación de gestión de proyectos llamada «TaskFlow». Vamos a ver paso a paso cómo estructurar la persistencia de datos.

Paso 1: Definir los Modelos con @Model

En un proyecto real, las relaciones son clave. Vamos a definir dos modelos: Project y Task.

import Foundation
import SwiftData

@Model
class Project {
    @Attribute(.unique) var id: UUID
    var name: String
    var createdAt: Date

    // Relación uno a muchos: Un proyecto tiene muchas tareas
    @Relationship(deleteRule: .cascade) var tasks: [Task] = []

    init(name: String) {
        self.id = UUID()
        self.name = name
        self.createdAt = Date()
    }
}

@Model
class Task {
    var title: String
    var isCompleted: Bool
    var dueDate: Date
    var project: Project?

    init(title: String, dueDate: Date = Date()) {
        self.title = title
        self.isCompleted = false
        self.dueDate = dueDate
    }
}

Nota de arquitectura: El uso de @Attribute(.unique) asegura que no tengamos duplicados, y .cascade en @Relationship garantiza que si borramos un proyecto, todas sus tareas asociadas se eliminen automáticamente de la base de datos.

Paso 2: Configurar el ModelContainer

Debemos inicializar nuestro contenedor en el punto de entrada de la aplicación (App.swift).

import SwiftUI
import SwiftData

@main
struct TaskFlowApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // Inyectamos el contenedor para ambos modelos
        .modelContainer(for: [Project.self, Task.self])
    }
}

Con solo esta línea de código, SwiftData se encarga de crear la base de datos SQLite, configurar las tablas y exponer el contexto a toda la jerarquía de vistas de SwiftUI.

Paso 3: Consultar y Mostrar Datos con @Query

En nuestra vista principal, podemos usar el wrapper @Query para recuperar los proyectos de forma ordenada y en tiempo real.

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext

    // Consulta ordenada por fecha de creación descendente
    @Query(sort: \Project.createdAt, order: .reverse) private var projects: [Project]

    @State private var newProjectName = ""

    var body: some View {
        NavigationStack {
            List {
                ForEach(projects) { project in
                    NavigationLink(project.name, value: project)
                }
                .onDelete(perform: deleteProjects)
            }
            .navigationTitle("Mis Proyectos")
            .toolbar {
                TextField("Nuevo Proyecto", text: $newProjectName)
                    .onSubmit {
                        addProject()
                    }
            }
        }
    }

    private func addProject() {
        guard !newProjectName.isEmpty else { return }
        let project = Project(name: newProjectName)

        // Insertamos el objeto en el contexto
        modelContext.insert(project)
        newProjectName = ""

        // No es necesario llamar a context.save() manualmente; 
        // SwiftData guarda automáticamente de forma periódica.
    }

    private func deleteProjects(offsets: IndexSet) {
        for index in offsets {
            modelContext.delete(projects[index])
        }
    }
}

SwiftData en el mundo real: Desafíos y soluciones

Llevar SwiftData a producción requiere conocer cómo se comporta bajo presión y en escenarios complejos.

1. Migración desde Core Data

Si ya tienes una app en producción usando Core Data, la buena noticia es que no tienes que empezar de cero. Dado que SwiftData utiliza el mismo almacenamiento SQLite subyacente, puedes realizar una migración progresiva.

  • Puedes usar clases de Core Data existentes y cargarlas en un ModelContainer de SwiftData compartiendo el mismo identificador de modelo de datos.

2. Sincronización con CloudKit

Habilitar la sincronización en la nube con SwiftData es extremadamente sencillo. Solo necesitas:

  • Activar las capacidades de CloudKit e iCloud en Xcode.
  • Asegurarte de que todas las propiedades de tus modelos tengan valores por defecto o sean opcionales.
  • Configurar relaciones con reglas de eliminación claras. SwiftData se encargará del resto en segundo plano de manera automática.

3. Operaciones pesadas en segundo plano

Para evitar bloquear la interfaz de usuario al importar miles de registros (por ejemplo, desde una API REST), debes realizar las operaciones en un hilo secundario utilizando un ModelActor.

@ModelActor
actor DataImporter {
    func importJSONData(records: [NetworkRecord]) {
        for record in records {
            let newTask = Task(title: record.title)
            modelContext.insert(newTask)
        }
        try? modelContext.save()
    }
}

Buenas prácticas para trabajar con SwiftData

Para asegurar el rendimiento y la escalabilidad de tu app, ten en cuenta los siguientes consejos:

  • Usa contenedores en memoria para pruebas unitarias y Previews: Evita escribir en el disco durante el desarrollo rápido.
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try ModelContainer(for: Project.self, configurations: config)
  • Limita el tamaño de las consultas: Si tienes miles de registros, no los traigas todos de golpe. Utiliza filtros en tu @Query o paginación.
  • Planifica tus migraciones: Aunque las migraciones sencillas son automáticas, si cambias drásticamente la estructura de tus datos, define un SchemaMigrationPlan explícito para evitar la pérdida de datos de tus usuarios.

Conclusión

SwiftData no es solo un lavado de cara para Core Data; es una redefinición completa de cómo los desarrolladores de Apple interactúan con la persistencia de datos. Al eliminar la complejidad visual y el código repetitivo, nos permite centrarnos en lo que realmente importa: crear grandes experiencias para el usuario.

Si estás iniciando un proyecto nuevo enfocado en iOS 17 o superior, SwiftData es la opción por defecto. Su curva de aprendizaje amigable y su robustez interna lo convierten en una herramienta imprescindible para el desarrollo moderno en Swift.

Deja una respuesta