← Todos los artículos

Migraciones de SwiftData: ligeras o personalizadas, y cuándo no necesitas un V2

El modelo de migración de esquemas de SwiftData es una mejora estructural frente al de Core Data, con una trampa en la que los equipos siguen cayendo: declarar un VersionedSchema nuevo para cambios que SwiftData resolvería automáticamente mediante valores predeterminados en línea. El resultado es un fallo en el dispositivo con el mensaje «Duplicate version checksums across stages detected», aunque el código se veía bien y compilaba sin errores. El modelo de migración real del framework se apoya en tres piezas (VersionedSchema, MigrationStage, SchemaMigrationPlan) y en tres tipos de migración: ligera automática, ligera declarada y personalizada1. La mayoría de los cambios de esquema son automáticos. Algunos requieren una etapa ligera declarada. Una pequeña minoría requiere una etapa personalizada con los closures willMigrate y didMigrate.

Este artículo recorre el modelo de migración contrastándolo con la documentación de Apple, nombra los casos que cubre cada tipo de migración y aborda la herencia de clases de iOS 26 junto con la situación de las migraciones en las betas de iOS 27. El marco es «qué declaro yo frente a qué resuelve SwiftData por mí», porque de esa decisión depende que la migración salga limpia o que la app falle en el primer arranque. La pregunta complementaria, cómo diseñar un esquema v1 para que estas migraciones sigan siendo baratas, se trata en El verdadero costo de SwiftData es la disciplina de esquema.

En resumen

  • Las migraciones de SwiftData combinan tres protocolos: VersionedSchema (una instantánea de los tipos de modelo en una versión), MigrationStage (una única transición de fromVersion a toVersion, con los casos .lightweight o .custom) y SchemaMigrationPlan (la lista ordenada de etapas)1.
  • Agregar una propiedad @Model nueva con un valor predeterminado en línea (var foo: Bool = false) no requiere un VersionedSchema nuevo. SwiftData resuelve el agregado automáticamente, como migración ligera. Declarar un V2 para eso provoca fallos con el mensaje «Duplicate version checksums across stages detected».
  • Las migraciones ligeras cubren: agregar, renombrar y eliminar entidades, atributos y relaciones; cambiar el tipo de una relación; declarar @Attribute(originalName:) para rastrear renombres; especificar reglas de borrado. La mayoría de los cambios de esquema entran aquí.
  • Las migraciones personalizadas (MigrationStage.custom(fromVersion:toVersion:willMigrate:didMigrate:)) cubren transformaciones de datos: dividir una columna en dos, calcular campos derivados, mover datos entre modelos. willMigrate recibe el contexto viejo; didMigrate, el nuevo.
  • iOS 26 agrega herencia de clases para los tipos @Model2. Los esquemas que adoptan herencia suben a una versión nueva con una etapa ligera desde la versión anterior de modelos planos.

El modelo de tres piezas

Una migración de SwiftData se compone de tres piezas.

VersionedSchema

Una instantánea de los tipos de modelo en una versión de esquema concreta1. El protocolo exige:

  • static var versionIdentifier: Schema.Version. Una terna de versión semántica (Schema.Version(1, 0, 0)).
  • static var models: [any PersistentModel.Type]. El arreglo de tipos @Model presentes en esta versión.
enum SchemaV1: VersionedSchema {
    static let versionIdentifier = Schema.Version(1, 0, 0)
    static var models: [any PersistentModel.Type] {
        [Item.self]
    }

    @Model
    final class Item {
        var name: String
        var createdAt: Date
        init(name: String, createdAt: Date) {
            self.name = name
            self.createdAt = createdAt
        }
    }
}

El patrón de enumeración con tipos anidados es la convención habitual. Cada VersionedSchema da un espacio de nombres propio a sus clases de modelo, de manera que varios esquemas con el mismo nombre de modelo pueden convivir en el código durante una migración.

MigrationStage

Una única transición entre dos tipos VersionedSchema3. Hay dos casos:

  • .lightweight(fromVersion: any VersionedSchema.Type, toVersion: any VersionedSchema.Type). Declara una transición que SwiftData resuelve sin código de la app. Los parámetros son los propios tipos VersionedSchema (por ejemplo, SchemaV1.self), no valores Schema.Version en crudo.
  • .custom(fromVersion:toVersion:willMigrate:didMigrate:). Declara una transición con código que se ejecuta antes o después de la migración de datos, o en ambos momentos. Los argumentos de versión llevan los mismos tipos que en .lightweight.

SchemaMigrationPlan

La lista ordenada de etapas que lleva el esquema desde cualquier versión anterior hasta la actual1.

enum AppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [SchemaV1.self, SchemaV2.self, SchemaV3.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2, migrateV2toV3]
    }

    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: SchemaV1.self,
        toVersion: SchemaV2.self
    )

    static let migrateV2toV3 = MigrationStage.custom(
        fromVersion: SchemaV2.self,
        toVersion: SchemaV3.self,
        willMigrate: { context in
            // Pre-migration: read old data, prepare it
            try context.save()
        },
        didMigrate: { context in
            // Post-migration: backfill new fields
            let descriptor = FetchDescriptor<SchemaV3.Item>()
            let items = try context.fetch(descriptor)
            for item in items {
                item.computedField = computeFromExisting(item)
            }
            try context.save()
        }
    )
}

El ModelContainer se configura con el esquema actual y con el plan de migración a la vez:

let container = try ModelContainer(
    for: SchemaV3.Item.self,
    migrationPlan: AppMigrationPlan.self,
    configurations: ModelConfiguration(...)
)

Al crear el contenedor, SwiftData lee la versión de esquema actual del almacén persistente, recorre las etapas del plan desde esa versión hacia adelante hasta la actual y aplica cada etapa en orden.

Qué resuelven automáticamente las migraciones ligeras

La mayoría de los cambios de esquema no requieren una etapa personalizada1:

  • Agregar un atributo con valor predeterminado. var foo: Bool = false en un @Model existente es automático.
  • Agregar una entidad nueva (clase de modelo). Los tipos nuevos aparecen cuando su VersionedSchema pasa a ser el actual; los datos existentes se conservan.
  • Quitar un atributo o una entidad. SwiftData descarta la columna o la tabla.
  • Renombrar un atributo o una entidad. Agrega @Attribute(originalName: "oldName") a la propiedad para conservar los datos; SwiftData mapea lo viejo a lo nuevo.
  • Cambiar el tipo de una relación. De uno a muchos, de muchos a muchos, etcétera.
  • Especificar reglas de borrado. @Relationship(deleteRule: .cascade) y agregados similares son ligeros.

Para los cambios de esta lista, el patrón correcto consiste en no declarar ningún VersionedSchema nuevo siempre que los tipos de modelo no cambien por lo demás. SwiftData ejecuta la migración ligera de forma automática contra el esquema existente.

La trampa: agregar un campo no exige un V2

El error más común en las migraciones de SwiftData: alguien agrega una propiedad nueva con valor predeterminado en línea (var foo: Bool = false) y luego declara un SchemaV2 que referencia los mismos tipos de modelo que SchemaV1. La compilación sale limpia. El primer arranque en un dispositivo con datos V1 existentes falla con Duplicate version checksums across stages detected, porque tanto SchemaV1 como SchemaV2 resuelven a la misma suma de verificación (los tipos de modelo no cambiaron de una forma que SwiftData perciba como distinta).

El patrón correcto: deja el VersionedSchema existente tal como está, agrega la propiedad nueva al modelo con un valor predeterminado en línea y deja que la migración ligera automática de SwiftData se encargue. No hace falta MigrationPlan, ni MigrationStage, ni V2.

// V1 schema
enum SchemaV1: VersionedSchema {
    @Model
    final class Item {
        var name: String
        // BEFORE: just these two properties
        var createdAt: Date
        // AFTER: add a third with inline default
        var isFavorite: Bool = false   // Lightweight, automatic
    }
}

El cambio var isFavorite: Bool = false se publica sin ninguna declaración de MigrationStage. El inicializador de ModelContainer que no pasa migrationPlan: funciona:

let container = try ModelContainer(
    for: SchemaV1.Item.self,
    configurations: ModelConfiguration(...)
)

El esquema V2 solo se vuelve necesario cuando un cambio no puede ser ligero: una transformación de datos, la división de un modelo, una reestructuración por herencia que exige lógica propia. En esos casos el V2 es real y un SchemaMigrationPlan orquesta la transición.

Cuándo se necesitan migraciones personalizadas

Las migraciones personalizadas justifican su complejidad en tres casos.

1. Dividir un campo en varios. Un campo String que contiene "Last, First" se convierte en dos campos, firstName y lastName. La migración tiene que leer el valor viejo, analizarlo y escribir los campos nuevos.

static let migrateV1toV2 = MigrationStage.custom(
    fromVersion: SchemaV1.self,
    toVersion: SchemaV2.self,
    willMigrate: nil,
    didMigrate: { context in
        let descriptor = FetchDescriptor<SchemaV2.Person>()
        let people = try context.fetch(descriptor)
        for person in people {
            let parts = person.fullName.split(separator: ", ", maxSplits: 1)
            person.lastName = String(parts.first ?? "")
            person.firstName = String(parts.dropFirst().first ?? "")
        }
        try context.save()
    }
)

El closure didMigrate se ejecuta contra el contexto del esquema nuevo, así que los campos nuevos están accesibles. Quizá haya que posponer la eliminación del viejo fullName hasta que los campos nuevos estén poblados; esa limpieza es una etapa posterior de V2 a V3.

2. Calcular campos derivados. Un @Attribute nuevo que depende de datos existentes hay que rellenarlo en el momento de la migración.

3. Mover datos entre modelos. Una reorganización en la que los datos de Item se reparten entre Item y un modelo Tag nuevo exige lógica propia para asignar las etiquetas a partir de los datos viejos.

El principio general: ligera cuando cambia la forma del esquema; personalizada cuando cambia la forma de los datos.

willMigrate frente a didMigrate

Las etapas personalizadas tienen dos closures, invocados en momentos distintos4.

willMigrate se ejecuta antes de que SwiftData aplique la migración de esquema. El contexto de modelo que recibe el closure es el del esquema viejo. Úsalo para capturar datos, desnormalizarlos o preparar estado auxiliar antes de que el esquema cambie por debajo.

didMigrate se ejecuta después de la migración de esquema. El contexto de modelo es el del esquema nuevo. Úsalo para rellenar campos nuevos, calcular datos derivados o cerrar la migración.

Cualquiera de los dos closures puede ser nil si no hace falta. La mayoría de las migraciones personalizadas usan solo didMigrate; willMigrate resulta útil cuando la migración necesita leer datos viejos que dejarán de estar accesibles tras el cambio de esquema.

El closure recibe un ModelContext y puede consultar, modificar y guardar. Está declarado como throwing: los errores se propagan fuera de la migración y la abortan.

iOS 26: herencia de clases para @Model

iOS 26 introduce la herencia de clases para los modelos de SwiftData2. Ahora los modelos pueden tener relaciones de padre e hijo:

@Model
class Vehicle {
    var make: String
    var year: Int
    init(make: String, year: Int) {
        self.make = make
        self.year = year
    }
}

@Model
final class Car: Vehicle {
    var doorCount: Int
    init(make: String, year: Int, doorCount: Int) {
        self.doorCount = doorCount
        super.init(make: make, year: year)
    }
}

Los esquemas que adoptan herencia suben a una versión nueva con una etapa de migración ligera desde la versión anterior de modelos planos. La transición es automática si la herencia conserva las propiedades existentes; los campos nuevos de la subclase siguen el patrón habitual de valores predeterminados en línea.

El patrón encaja en los casos en que varios tipos @Model comparten características: un padre Vehicle con hijos Car, Truck y Motorcycle; un padre Account con hijos CheckingAccount y SavingsAccount. Las propiedades compartidas viven en el padre; las particularidades, en los hijos.

iOS 27: el modelo de migración se mantiene, el almacén se vuelve observable

Las betas de iOS 27 no cambian nada de la maquinaria de migración en sí. VersionedSchema, MigrationStage y SchemaMigrationPlan se mantienen intactos, y todos los patrones anteriores se aplican tal cual. Lo que iOS 27 agrega queda al lado de las migraciones, no dentro de ellas: una superficie nueva de «Data store observation» con dos tipos, ResultsObserver e HistoryObserver, más una opción @Attribute(.codable) que almacena una propiedad a través de su representación Codable6.

Dos de esos agregados merecen mención en una guía de migraciones.

@Attribute(.codable) reduce la presión migratoria futura. Un tipo de valor Codable almacenado de forma declarativa significa menos casos en los que habría que aplanar una estructura en columnas paralelas y, más adelante, escribir una etapa personalizada para volver a armarla. Los esquemas que adoptan la opción en propiedades nuevas siguen las reglas de valores predeterminados en línea que vimos antes: es una opción de atributo, no un cambio en la forma del esquema6.

HistoryObserver cierra el círculo después de una migración personalizada. Un relleno hecho en didMigrate escribe filas de las que el resto de la app (y cualquier widget o extensión que observe el almacén) necesita enterarse. En iOS 27, un observador que sigue el historial persistente mediante HistoryObserver ve llegar las transacciones de la migración y puede llamar a ModelContext.fetchHistory para leer exactamente lo que cambió, filtrado por tipo de modelo y autor de la transacción, en vez de recargarlo todo6. La historia completa de la observación se cuenta en SwiftData en iOS 27: observación e historial.

La conclusión para planificar: nada en las betas de iOS 27 obliga a subir la versión del esquema, y ningún código de migración hay que reescribirlo. Adopta los tipos nuevos de observación allí donde tu lógica de reconciliación posterior a la migración solía sondear o recargar.

Probar las migraciones

Una migración que compila no es una migración lista para publicar. Tres patrones de prueba que vale la pena ejecutar antes de lanzar.

1. Prueba de ida y vuelta sobre una copia de la base de datos de producción. Toma una base de datos reciente con forma de producción (o genera datos V1 sintéticos desde las pruebas), ábrela con el contenedor que conoce el V2 y verifica que los datos migren correctamente. La prueba atrapa errores de migración personalizada que el verificador de tipos no puede ver.

2. La versión vieja sigue arrancando. Compila la versión anterior de la app, ejecútala una vez para producir datos V1, luego compila la versión nueva y verifica que arranque sin fallar. La prueba atrapa la trampa de «Duplicate version checksums» y errores de declaración parecidos.

3. Recuperación ante una migración fallida. ¿Qué pasa si la migración lanza un error? El comportamiento de SwiftData depende de la configuración del contenedor; en apps de producción, un error de migración sin manejar no debe borrar en silencio los datos del usuario. Prueba el camino de fallo de forma explícita y decide qué hace la app: revertir, preguntar o recuperar desde una copia de seguridad.

El artículo Una sola fuente de verdad, del mismo conjunto, trata la pregunta emparentada de qué ocurre cuando un almacén de SwiftData se reemplaza mediante sincronización entre procesos. Las migraciones son el equivalente local de ese patrón.

Publicar migraciones entre procesos y mostrar el progreso

Dos detalles operativos que la documentación no pone en primer plano, pero que el equipo de SwiftData señaló en la WWDC 20265: dónde se ejecutan las migraciones cuando una app tiene widgets o extensiones, y cómo alimentar una interfaz de progreso mientras una migración corre.

Un solo proceso es dueño de la migración. Los widgets y las extensiones no reciben los mismos recursos de ejecución que la app principal, así que no pueden realizar una migración de forma segura. La recomendación es mantener el SchemaMigrationPlan1 completamente fuera de los targets de widget y de extensión, y no migrar nunca desde ellos. Elige un proceso, normalmente la app principal, como dueño de la base de datos. Si un widget abre el contenedor y el almacén en disco está en un esquema sin versionar (más viejo), la apertura falla. Trata ese error como la señal de que hace falta una migración: muestra una interfaz que le pida al usuario abrir la app principal, deja que la app realice la migración y que escriba la versión de esquema migrada en un UserDefault compartido. El widget lee ese valor la próxima vez y abre el contenedor en la versión a la que la app ya migró. El patrón mantiene a un único escritor al mando y evita que dos procesos compitan por hacer evolucionar el mismo archivo.

El progreso se calcula a partir del número de etapas, no del tiempo transcurrido. SwiftData no expone ninguna API dedicada al progreso de la migración5. Para alimentar un indicador de progreso, cuenta el total de etapas de migración personalizadas del plan y sobrescribe el manejador didMigrate de cada etapa4 para que cada una informe su posición: «etapa N de M». El número refleja etapas completadas, no tiempo transcurrido, así que la barra avanza a saltos discretos en lugar de hacerlo de forma continua. La decisión de diseño que acompaña a esto es qué muestra la app durante la migración: un simple indicador giratorio se lee como un cuelgue y la gente abandona. Mantén la app parcialmente usable donde los datos lo permitan o, como mínimo, describe qué agrega cada etapa (las funciones nuevas que desbloquea la migración), para que la espera se lea como avance hacia algo y no como tiempo muerto.

Modos de fallo habituales

Tres patrones sacados de los registros de errores de SwiftData.

Declarar un V2 para un cambio que SwiftData resolvería automáticamente. El fallo de «Duplicate version checksums». Solución: no declares un esquema nuevo para agregar propiedades con valor predeterminado en línea; deja que SwiftData las resuelva sola.

Código de migración personalizada que no guarda. Un closure didMigrate que modifica entidades pero no llama a context.save() produce una migración que se ejecuta una vez, tira su trabajo y vuelve a correr en cada arranque (porque parece inconclusa). Solución: todo closure que modifique datos debe llamar a try context.save() antes de terminar.

Renombrar una propiedad sin @Attribute(originalName:). SwiftData trata la propiedad nueva como nueva y la vieja como eliminada; los datos que había en la propiedad vieja se pierden. Solución: declara @Attribute(originalName: "oldName") var newName: ... para que SwiftData arrastre los datos a través del renombre.

Qué significa este patrón para las apps de iOS 26 en adelante

Tres conclusiones.

  1. Por defecto, nada de escaleras de VersionedSchema. Agregar propiedades con valores predeterminados en línea, borrar campos sin uso, renombrar con @Attribute(originalName:): todo ligero y automático. La escalera de VersionedSchema es para los cambios que SwiftData realmente no puede resolver sola (transformaciones de datos, lógica propia, reestructuraciones por herencia).

  2. Usa MigrationStage.custom para transformaciones de datos, no para cambios en la forma del esquema. Los closures willMigrate y didMigrate son para código que opera sobre los datos, no para declarar que el esquema cambió. Los cambios de forma del esquema pasan por etapas ligeras.

  3. Prueba las migraciones con datos V1 reales, no solo con datos sintéticos. Las migraciones que superan idas y vueltas sintéticas todavía pueden fallar con datos de forma real y sus casos límite: campos nulos que el esquema no contemplaba, conjuntos grandes que agotan el tiempo de espera, etcétera. El costo de probar es pequeño; el costo de un fallo de migración en el primer arranque es real.

El conjunto completo dedicado al ecosistema de Apple: App Intents tipados; servidores MCP; la pregunta del enrutamiento; Foundation Models; la distinción entre el LLM de ejecución y el de herramientas; tres superficies; el patrón de la única fuente de verdad; Dos servidores MCP; hooks para el desarrollo en Apple; Live Activities; el contrato de ejecución de watchOS; de qué está hecho SwiftUI; el modelo mental espacial de RealityKit; la disciplina de esquema en SwiftData; los patrones de Liquid Glass; la publicación multiplataforma; la matriz de plataformas; el framework Vision; los Symbol Effects; la inferencia con Core ML; la API de Writing Tools; Swift Testing; el Privacy Manifest a fondo; la accesibilidad como función de plataforma; la tipografía de SF Pro; los patrones espaciales de visionOS; el framework Speech; sobre qué me niego a escribir. El centro de todo es la serie Ecosistema Apple. Para un contexto más amplio de iOS con agentes de IA, consulta la guía de desarrollo de agentes en iOS.

Preguntas frecuentes

¿Siempre necesito un SchemaMigrationPlan?

No. Las apps con una sola versión de esquema (el lanzamiento inicial, o apps que solo han hecho cambios ligeros) no necesitan un SchemaMigrationPlan. El inicializador de ModelContainer acepta directamente los modelos del esquema. El parámetro migrationPlan: se vuelve necesario la primera vez que se declara una etapa de migración personalizada (o la primera vez que quieres declarar una escalera de versiones explícita).

¿Cómo sé si mi cambio es ligero?

La lista de cambios elegibles para migración ligera según Apple1: agregar entidades, atributos y relaciones, quitarlos, renombrar con @Attribute(originalName:), cambiar la cardinalidad de una relación, especificar reglas de borrado. Si el cambio encaja en alguno de estos casos y la estructura de las clases de modelo no cambia por lo demás, la migración es automática y no hace falta ninguna escalera de VersionedSchema. Si el cambio exige transformar datos (calcular, dividir, mover), es personalizado.

¿Se pueden definir willMigrate y didMigrate a la vez?

Sí. Cada closure es opcional por separado, pero se pueden proporcionar ambos. willMigrate se ejecuta contra el contexto del esquema viejo antes de que SwiftData migre; didMigrate se ejecuta contra el contexto del esquema nuevo después. Entre los dos cubren la preparación y el cierre.

¿Qué pasa si una migración lanza un error?

El error se propaga fuera de la inicialización del ModelContainer. El contenedor no llega a abrirse. El comportamiento de la app depende de cómo se maneje el error: algunas muestran una interfaz de recuperación, otras intentan restaurar desde una copia de seguridad, otras borran el almacén dañado y empiezan de cero. SwiftData no borra en silencio los datos del usuario cuando una migración falla; manejar el fallo le corresponde a la app.

¿Cómo pruebo una migración sin tocar los datos de producción?

Arma un target de pruebas que cree un ModelContainer apuntando a una URL de archivo temporal, lo llene con datos V1 y luego lo abra con el contenedor nuevo que incluye el plan de migración. Verifica que los datos migrados coincidan con lo esperado. El patrón funciona tanto en pruebas unitarias como de integración; para resultados más realistas, usa una copia de una base de datos real con forma de producción.

¿La herencia de clases de iOS 26 funciona con esquemas existentes?

Sí, con una migración ligera. Las apps que adoptan herencia suben a una versión de esquema nueva (V4, por ejemplo) y declaran un MigrationStage.lightweight(fromVersion: V3.self, toVersion: V4.self). Las propiedades planas de la clase padre se conservan y las propiedades específicas de la subclase se agregan con valores predeterminados en línea. La migración ligera de SwiftData absorbe el cambio estructural.

Referencias


  1. Documentación de Apple Developer: referencias de los protocolos VersionedSchema y SchemaMigrationPlan. El modelo de migración. Véase también la guía relacionada Adopting SwiftData for a Core Data app para el relato completo de la evolución de esquemas. 

  2. Apple Developer: SwiftData: Dive into inheritance and schema migration (WWDC 2025, sesión 291). La introducción de la herencia de clases de SwiftData en iOS 26. 

  3. Documentación de Apple Developer: MigrationStage, con los casos .lightweight(fromVersion:toVersion:) y .custom(fromVersion:toVersion:willMigrate:didMigrate:)

  4. Documentación de Apple Developer: MigrationStage.custom(fromVersion:toVersion:willMigrate:didMigrate:) para la firma del caso. La semántica de que willMigrate se ejecuta contra el contexto viejo y didMigrate contra el nuevo está documentada en la sesión 291 de la WWDC 2025, SwiftData: Dive into inheritance and schema migration, la misma sesión citada para el agregado de herencia en iOS 26. 

  5. SwiftData Group Lab de la WWDC 2026 (sesión 8017). Parafraseado a partir de una grabación del SwiftData Group Lab de la WWDC 2026 transcrita localmente; Apple no publica subtítulos oficiales de los labs. El criterio sobre migraciones en widgets y extensiones (un solo proceso es dueño de la migración, el camino de error es la señal de migración, la versión migrada se guarda en un UserDefault) y la técnica de progreso basada en el conteo de etapas (sobrescribir el manejador didMigrate de cada etapa para informar la etapa N de M, ya que no existe una API de progreso dedicada) fueron descritos por el panel de ingeniería de SwiftData. Los símbolos SchemaMigrationPlan y didMigrate de MigrationStage.custom están confirmados contra la documentación de Apple Developer citada en 1 y 4; la ausencia de una API de progreso dedicada refleja el propio planteamiento del panel durante el lab. 

  6. Documentación de Apple Developer: ResultsObserver e HistoryObserver (ambos en beta de iOS 27.0, bajo el tema «Data store observation» de SwiftData), y Schema.Attribute.Option.codable (beta de iOS 27.0), «uses the property’s codable representation to store the property». Según la sesión 274 de la WWDC26, What’s new in SwiftData, HistoryObserver expone un eventCounter observable que se incrementa cuando llegan transacciones nuevas, y el código responde llamando a ModelContext.fetchHistory con filtros por tipo de modelo y autor de la transacción. 

Artículos relacionados

El costo real de SwiftData es la disciplina de esquema

La API de SwiftData son dos macros. El costo aparece después de publicar. Los campos opcionales son la migración barata;…

24 min de lectura

SwiftData en iOS 27: Observación e historial

iOS 27 da a SwiftData observación de cambios con ResultsObserver, historial persistente con HistoryObserver y almacenami…

14 min de lectura

Instalar y actualizar Codex CLI: Mac, Linux, Windows

Todas las formas de instalar, actualizar, fijar versión y desinstalar la CLI de OpenAI Codex -- script de instalación, n…

20 min de lectura