Guía esencial para comenzar con SwiftData
Libranner Santos
01 septiembre, 20236min de lectura
SwiftData es una capa de persistencia diseñada para aplicaciones en los sistemas operativos de Apple, ofreciendo una alternativa simplificada a Core Data. Su enfoque declarativo la hace ideal para una integración fluida con SwiftUI.
Con SwiftData, puedes llevar a cabo todas las operaciones esenciales de persistencia de datos, incluyendo lectura, almacenamiento, actualización y eliminación de registros. Además, facilita la gestión de migraciones para adaptar los esquemas de datos según las necesidades.
El SwiftData Stack
Al igual que Core Data tenía su Core Data Stack, SwiftData cuenta con su propia estructura de SwiftData Stack. Esta estructura engloba distintos componentes fundamentales para su funcionamiento: el ModelContainer
y el ModelContext
.
El ModelContainer
se encarga de las operaciones de persistencia de los modelos, mientras que el ModelContext
registra los cambios realizados en los modelos, permitiendo reaccionar ante los mismos. Estas reacciones, como por ejemplo cuando eliminamos un registro, son inmediatamente manejadas por SwiftUI manteniendo las vistas actualizadas.
Configurando el Stack
Configurar tu ModelContainer
con SwiftData es sencillo. Solo debes asegurarte de usar el modificador modelContainer(for:inMemory:isAutosaveEnabled:isUndoEnabled:onSetup:)
en una escena de tu aplicación, preferiblemente en la escena principal. Por ejemplo:
import SwiftData@mainstruct StudentApp: App {var body: some Scene {WindowGroup {StudentsView()}.modelContainer(for: [Student.self, Project.self])}}
Para acceder al ModelContext
, utiliza la variable de entorno modelContext
de la siguiente manera:
import SwiftUIstruct ContextView : View {@Environment(\.modelContext) private var context}
Modelando los datos
Para modelar nuestros datos o, dicho de otra forma, definir las entidades que conforman nuestro esquema podemos usar clases anotadas con macros. A continuación, un ejemplo:
import SwiftData@Modelclass Student {@Attribute(.unique) var identification: Stringvar name: Stringvar age: Intinit(identification: String, name: String, age: Int) {self.identification = identificationself.name = nameself.age = age}}
Hasta este momento es obligatorio crear el constructor (init) si queremos usar el macro
@Model
. Esto podría cambiar en el futuro.
En este fragmento de código, hemos definido la clase Student
con tres propiedades: identification
, name
y age
. La anotación @Model
indica a SwiftData que esta clase es un modelo de nuestro esquema. Además, hemos empleado el macro @Attribute(.unique)
para especificar que el campo identification
debe ser único.
Además de @Attribute
, puedes utilizar @Relationship
y @Transient
para proporcionar metadatos que guíen el comportamiento de SwiftData para una propiedad dada. Por ejemplo:
import SwiftData@Modelclass Student {@Attribute(.unique) var identification: Stringvar name: Stringvar age: Int@Transientvar alias: String = ""@Relationship(deleteRule: .cascade)var projects: [Project]}
En este caso, hemos ampliado la clase Student
con dos nuevas propiedades:
alias
, marcada con@Transient
, esto indica a SwiftData que esta propiedad debe ignorarse y no ser persistida.projects
, marcada con@Relationship(deleteRule: .cascade)
, establece una relación uno a muchos con la entidadProject
, con la regla de eliminacióncascade
, lo que instruye a SwiftData a eliminar los proyectos asociados cuando se borre un estudiante.
Existen más opciones que puedes utilizar como deleteRule
:
noAction
. No hace nada.nullify
. Hace nula la referencia al modelo eliminado.deny
. No permite la eliminación de un registro si este tiene una relación con otro.
Leer datos
Para leer datos con SwiftData, utiliza el macro #Predicate
. Veamos un ejemplo:
let age = 20let studentPredicate = #Predicate<Student> {$0.age < age}
Para usar este macro asegúrate de importar Foundation
.
En este código, definimos el criterio de búsqueda mediante la variable age
, con un valor de 20
. Luego, creamos un predicado usando el macro #Predicate
, especificando el tipo de datos Student
y una condición dentro del cierre: $0.age < age
. Estas líneas bastan para realizar una lectura con SwiftData y obtener todos los estudiantes menores de 20 años.
Para consultas más avanzadas, puedes usar FetchDescriptor
:
let descriptor = FetchDescriptor<Student>(predicate: studentPredicate,sortBy: [SortDescriptor(\Student.name)])let students = try context.fetch(descriptor)
En este caso, hemos indicado que los resultados se ordenen según el campo name
utilizando sortBy: SortDescriptor(\Student.name)
en el descriptor
. Luego, utilizamos try context.fetch(descriptor)
para ejecutar la consulta.
SwiftData ❤️ SwiftUI
Para leer registros, utiliza el nuevo macro @Query(filter:sort:order:transaction:)
, que acepta varios parámetros para filtrar y ordenar los resultados. Aquí hay un ejemplo de cómo listar estudiantes en SwiftUI:
struct StudentsView: View {// 1@Environment(\.modelContext) private var context// 2@Query(sort: \Student.name) private var students: [Student]var body: some View {List {// 3ForEach(students, id: \Student.identification) { student inVStack {Text(student.name)}}}}}
En este código:
- Obtenemos una referencia al contexto usando la variable de entorno
modelContext
. - Con
@Query
, solicitamos todos los estudiantes ordenados porname
y los almacenamos en la variablestudents
. - Utilizamos un
ForEach
para crear la lista de estudiantes, utilizando la propiedadidentification
como identificador.
Insertar datos
Para insertar un registro, podemos utilizar el siguiente código:
// 1let student = Student(identification: "123", name: "John", age: 18)// 2context.insert(student)// 3do {try context.save()} catch {print(error.localizedDescription)}
Estos son los pasos:
- Creamos el objeto que deseamos insertar.
- Usamos el
ModelContext
para llamar ainsert
, pasando nuestro objeto. - Finalmente, utilizamos
try context.save()
para guardar los cambios.
Eliminar datos
Para eliminar un registro, necesitamos una referencia al objeto y luego usamos context.delete(student)
:
context.delete(student)try context.save()
Actualizar datos
Al igual que para eliminar necesitamos una referencia al objeto. Luego realizamos los cambios deseados y llamanos context.save()
:
student.name = "Luis"try context.save()
Aunque para inserción y eliminación de datos hemos llamado a
context.save()
directamente, al utilizar SwiftUI, SwiftData guarda automáticamente los cambios en la base de datos. Sin embargo, es recomendable realizar esta operación directamente, ya que no se sabe con certeza cuándo SwiftData llevará a cabo estas acciones.
Otros datos interesantes sobre SwifData
- Se integra con CloudKit, lo que permite compartir datos entre dispositivos.
- Puedes utilizar Core Data y SwiftData en un mismo proyecto, ya que comparten la misma arquitectura.
- Xcode proporciona la funcionalidad para convertir entidades de Core Data en clases, lo que te permite utilizarlas con SwiftData.
- SwiftData admite tipos de datos complejos como
struct
,enum
,Codables
yCollections
.