View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Videos

Abrir menú Cerrar menú
  • Colecciones
  • Temas
  • Todos los videos
  • Información

Más videos

  • Información
  • Resumen
  • Transcripción
  • Código
  • SwiftData: Analiza a detalle la herencia y migración de esquemas

    Descubre cómo usar la herencia de clases para modelar tus datos. Aprende a optimizar las consultas y migrar sin problemas los datos de tu app para usar la herencia. Explora la subclasificación para crear gráficos de modelos, crear búsquedas y consultas eficientes e implementar migraciones de esquemas sólidas. Comprende cómo usar el historial observable y persistente para un seguimiento eficiente de los cambios.

    Capítulos

    • 0:00 - Introducción
    • 2:11 - Aprovecha la herencia de clases
    • 7:39 - Evolución de datos con la migración
    • 11:27 - Adaptación de los datos obtenidos
    • 13:54 - Observación de cambios en los datos
    • 18:28 - Próximos pasos

    Recursos

    • Adopting SwiftData for a Core Data app
    • Building rich SwiftUI text experiences
    • SwiftData
      • Video HD
      • Video SD

    Videos relacionados

    WWDC25

    • Novedades de SwiftUI

    WWDC24

    • Track model changes with SwiftData history
  • Buscar este video…

    Hola, soy Rishi Verma, ingeniero del equipo de SwiftData. Quiero darte la bienvenida a "SwiftData: explora la herencia y la migración de esquemas”. SwiftData se introdujo en iOS 17 y te permite modelar y conservar los datos de tu app en Swift en todas las plataformas de Apple. Te permite escribir código rápido, eficiente y seguro aprovechando las modernas características del lenguaje Swift. Y eso continúa en este video con una introducción al uso de la herencia de clases y al aprendizaje de cuándo la herencia es la opción adecuada. Con la adopción de la herencia y la evolución del esquema, discutimos las estrategias de migración utilizadas para preservar los datos, tras lo cual exploraremos algunas formas de adaptar las recuperaciones y consultas de SwiftData para obtener un rendimiento óptimo. Por último, hablaremos sobre cómo observar cambios en los modelos realizados local y remotamente. Desde hace algunas versiones, hemos estado usando una app conocida, SampleTrips. Es una app escrita en SwiftUI para llevar un registro de todos los viajes que he planeado. Para usar SwiftData con los modelos de esta app, solo debo importar la estructura y decorar cada modelo con la macro Model.

    Y en la definición de la app, añadimos el modificador modelContainer en WindowGroup, que indica a toda la jerarquía de vistas sobre el modelo Trip. Con modelContainer conectado, ahora puedo actualizar mi vista para aprovechar la macro Query. Eliminemos estos datos estáticos y, en su lugar, poblemos la vista con la macro Query, que generará el código para recuperar los viajes del contenedor del modelo.

    Eso es todo. La app ahora guarda todos los viajes que creo y se adapta perfecto a mis vistas SwiftUI. SwiftData no solo ofrece persistencia con facilidad, sino también modelado y migración del esquema, gestión de gráficos, sincronización con CloudKit y mucho más. Y la última novedad que llega a SwiftData es la herencia de clases. Una novedad de iOS 26 es la posibilidad de crear un gráfico de modelos que aprovecha la herencia. La herencia de clases es una herramienta poderosa. Veamos cuándo la herramienta es adecuada. La herencia funciona bien cuando tus modelos forman una jerarquía natural y comparten características. El modelo Trip tiene las propiedades destination, startDate y endDate, que todo viaje necesita para que sepamos a dónde vamos y cuándo. Y así, cualquier nueva subclase de Trip ya tendrá estas propiedades y cualquiera de los otros comportamientos compartidos definidos en Trip. El modelo Trip también es un dominio amplio. Hay tantos viajes que hacemos a lo largo de la vida. Una nueva subclase de Trip debe ser un subdominio natural que encaje en el dominio amplio Trip.

    En SampleTrips, muchos de los viajes se dividen en dos subdominios naturales: viajes personales y de negocios. Con estos dos nuevos modelos que expresan subdominios naturales de un viaje, quiero añadir propiedades y comportamientos específicos de estas subclases. Para los viajes personales, añadiré una enumeración que refleje el motivo por el que podría realizar ese viaje en concreto. Y para los de negocios, quiero añadir una propiedad para anotar mis gastos diarios, así sabré cuánto debo gastar en mi próxima aventura laboral. Hagámoslo en SampleTrips y creemos una experiencia más completa. Esta es la clase Trip con las propiedades que queremos compartir con las subclases. Añadamos dos nuevas subclases a nuestra app Trips, una para mis viajes de negocios y otra para los personales. También debo marcarlas con @available en iOS 26 o posterior para que se alineen con la compatibilidad con herencia de SwiftData. Ahora añadamos las propiedades específicas del subdominio a nuestras subclases. Puedo añadir un viático a BusinessTrip y establecer su valor inicial. Y para el PersonalTrip, añadimos una enumeración Reason para registrar el motivo por el que viajamos. Espera, queda una cosa que debemos hacer: actualizar el esquema para incluir las nuevas subclases. Añadamos BusinessTrip y PersonalTrip a modelContainer y estamos listos. SampleTrips está lista para registrar los nuevos viajes personales en azul y los de negocios en verde sin necesidad de ningún código especial. Aunque la herencia de clases es una herramienta poderosa, no es adecuada para los problemas. Analicemos cuándo usar herencia.

    Hay algunos casos en los que la herencia es la opción adecuada. Si tus modelos expresan de forma natural una relación jerárquica y tienen características comunes que deseas ampliar, entonces la herencia puede ser la opción correcta, ya que tus tipos forman una relación “es un”.

    Cuando usamos nuestros modelos heredados, sabemos que un viaje personal ES un viaje, lo que significa que cada vez que trabajo con un tipo Trip, como en el Query de Trip que se muestra aquí, puedo esperar encontrar todo tipo de viajes, incluidos los viajes personales y de negocios, así como solo instancias de la clase principal Trip. Vemos nuestros viajes representados como aviones con el mismo color que nuestra IU, que vuelan desde el contenedor del modelo hasta el contexto que respalda la consulta. Sin embargo, no debemos usar la herencia para compartir características comunes entre los modelos. Por ejemplo, si creáramos subclases para todos nuestros modelos que tienen una propiedad name, nuestra jerarquía de clases tendría muchos subdominios que solo comparten una propiedad para un propósito común y todas las demás características están aisladas en sus subdominios. Y como estos subdominios no forman una jerarquía natural, es mejor expresarlos como una conformidad con el protocolo. La conformidad con el protocolo permite que los distintos dominios compartan comportamientos, pero no otras características que no estén relacionadas. Otra razón para usar la herencia depende de cómo consultes o obtengas tus modelos.

    Hay varias formas de consultar datos. Nosotros usamos la macro Query para buscar todos los viajes del contenedor del modelo y así generar nuestras vistas. Este es un ejemplo de una búsqueda profunda.

    Si solo hacemos búsquedas profundas, es decir, si siempre buscamos todos los viajes y solo usamos el tipo Trip, debemos pensar que PersonalTrip o BusinessTrip son propiedades de Trip en vez de subclases. Sin embargo, si tus consultas o búsquedas solo recopilan los tipos de clase hoja, esto se conoce como búsqueda superficial. En este caso, consideraríamos simplificar nuestros modelos, ya que Trip nunca se consulta ni se usa como tipo. Pero si usas búsquedas profundas y superficiales, la herencia te resultará útil, ya que a menudo vas a buscar todos los viajes o un subtipo específico, como PersonalTrips, para generar una vista adaptada a ese tipo. Dediquemos un momento a ver cómo actualizar la app Trips para que solo muestre viajes personales o de negocios.

    Usemos un control segmentado para poder mostrar todos los viajes y luego las subclases específicas.

    El segmento seleccionado se usa para impulsar un predicado que determinará si la clase es de un tipo particular mediante la palabra 'is'. Por ejemplo, aquí compruebo si se trata de un PersonalTrip. Luego ingresamos el predicado y ordenamos por startDate del viaje para iniciar nuestra Query. Veámoslo en la app. Empieza en la vista de viajes, así puedo ver todos mis viajes. Luego puedo limitar las vistas a las subclases específicas. ¡Qué maravilla! Así es como aprovechamos la herencia de clases en iOS 26. Pero, aún no terminamos. Acabamos de hacer cambios importantes en el esquema y deberíamos pensar en lo que significa para la app actual y cómo migrar los datos. SampleTrips ha pasado por varias evoluciones en las últimas versiones. Tomemos un momento para capturar todo esto en esquemas versionados y un plan de migración de esquemas, de modo que nuestra app conserve los datos del usuario al actualizar SampleTrips.

    Todo comenzó con nuestro primer video, en el que presentamos SwiftData en iOS 17 y usamos Trips para guiar la adopción. A través de esos videos introductorios, aprendimos a hacer que el nombre de un viaje sea único y cómo modificar el nombre original de una propiedad y conservar los datos de la migración.

    Para iOS 17, creamos nuestro esquema versionado con un nuevo identificador de versión 2.0 y mostramos el modelo Trip modificado con un nombre único y fechas de inicio y finalización renombradas.

    Luego, añadimos una etapa de migración personalizada para poder eliminar los duplicados de viajes existentes. Aquí usamos la función fetch de ModelContext para poder buscar todos los viajes y así poder eliminar los duplicados.

    En iOS 18, aprovechamos el índice y las macros únicas, al tiempo que marcamos las propiedades que queríamos conservar al eliminarlas.

    Esto nos permite identificar nuestros modelos después de eliminarlos del almacén de datos.

    En nuestro esquema versionado para iOS 18, lo marcamos como versión 3 y capturamos los cambios en el modelo Trip. Nuevo uso de las macros únicas e índices para garantizar que nuestros datos no estén duplicados y sean eficaces para búsquedas y consultas. También marcamos esas mismas propiedades con valor conservado al eliminarlas, para poder identificar el viaje eliminado cuando consumimos el historial persistente.

    También añadimos otra etapa de migración personalizada para eliminar los duplicados de los viajes al migrar de la versión 2 a la 3. Ahora, en iOS 26, añadiremos la versión 4 con subclases y una etapa de migración ligera. En nuestro esquema de versión actual, lo marcamos como versión 4 y enumeramos todos los modelos de nuestro esquema con las nuevas subclases. Y como marcamos nuestras subclases con iOS 26 o posterior, también lo hacemos con el esquema de versión. Y necesitamos añadir una fase de migración ligera de la versión 3 a la versión 4 con la misma disponibilidad que antes. Y con nuestro esquema de versión final y nuestra etapa de migración construidos, podemos encapsular todo esto en un plan de migración de esquemas para poder indicar el orden de los esquemas de versión y las etapas de migración que se deben ejecutar. El plan de migración consiste en una matriz de esquemas en orden en que se lanzaron. Y cuando iOS 26 esté disponible, añadiremos nuestro esquema más reciente con subclases y, luego, una serie de etapas de migración para poder pasar de una versión a la siguiente. Así es como construimos el plan de migración del esquema. Ya creamos nuestro esquema de versiones y el plan de migración de esquemas correspondiente, el siguiente paso es aprovecharlos al crear el contenedor de modelos SampleTrips. Volvamos a modelContainer y actualicémoslo para utilizar una contenedor de modelos con un plan de migración de esquemas. Comenzamos añadiendo una nueva propiedad de contenedor a la app en la que crearemos nuestro esquema versionado con la versión 4 y daremos el plan de migración del esquema al inicializador de ModelContainer. Y ahora actualizamos nuestro modificador modelContainer para usar el nuevo contenedor migrable. Con eso listo, nos aseguramos de que actualizaciones de SampleTrips para aprovechar la herencia puedan migrar fácilmente mediante distintas versiones que lanzamos, mientras conservamos los datos de cliente. Una vez que resolvimos la migración, es hora de pensar en cómo mejorar consultas y las búsquedas que usamos para impulsar las vistas y etapas de migración. La última vez actualizamos la consulta con el segmento mediante Predicate, pero en un video anterior teníamos una barra de búsqueda. Volvamos a añadirla y pasemos al tratamiento de búsqueda que introdujo un cliente.

    Construimos un predicado con el texto de búsqueda proporcionado. Verificamos si el texto está vacío. Y si no, construimos un predicado compuesto para ver si el nombre o destino del viaje contiene el texto dado. Ahora, construyamos un predicado compuesto con el predicado de búsqueda y el predicado de clase. Y por último, actualizamos nuestro inicializador de consulta para tomar el nuevo predicado compuesto. Con esa actualización, puedo tocar la barra de búsqueda, ingresar algún texto para filtrar los viajes e incluso limitarlo aún más con un control segmentado. Filtrar y ordenar son algunas formas de personalizar consultas y búsquedas. Exploremos otras formas de personalizar búsquedas de SwiftData. Esta es la etapa de migración personalizada de la versión 1 a la 2, usaremos el bloque willMigrate para obtener todos los viajes. Sin embargo, en mi lógica de eliminación de duplicados, solo accedo a la propiedad única, name, ya que es la propiedad única en la versión 2, y la uso para asegurarme de que no haya otros duplicados. Como name es la propiedad a la que accedo, puedo actualizar el fetchDescriptor para utilizar propertiesToFetch con el nombre, nuestros modelos Trip solo empaqueten los datos que necesitamos durante migración. Además, si sabemos que podemos atravesar una relación particular, en este caso, sé que reasignaré una vivienda si encuentro un duplicado, podemos hacer la misma mejora con relationshipsToPrefetch. Agreguemos aquí la relación livingAccommodation.

    Adoptamos las propiedades de prefetch, también podemos actualizar el código del widget existente en SampleTrips para que sea más eficiente. En el widget SampleTrips, tenemos una consulta para el viaje más reciente. Sin embargo, se puede mejorar para que solo obtengamos un único valor. El código del widget solo aprovecha el primer resultado de búsqueda, pero podemos hacerlo eficiente estableciendo el límite de búsqueda.

    Al establecerlo, el widget obtendrá el primer viaje que coincida con el predicado y no se preocupará por un escenario que tenga vacaciones planeadas a futuro. Ahora que mejoramos nuestras consultas y búsquedas, exploremos cómo saber cuándo ha cambiado tu modelo. Todos los modelos persistentes son observables, por lo que usar withObservationTracking para reaccionar a los cambios en las propiedades de interés de modelos. Si quisiéramos observar los cambios en las fechas de inicio y fin de nuestro viaje, podríamos añadir la función datesChangedAlert para que, si el usuario cambia las fechas, muestre alerta.

    Podemos observar cambios locales realizados en nuestros PersistentModels, y es muy útil para los cambios realizados. Para obtener información sobre novedades de Observable, consulta What’s new in Swift. Sin embargo, no todos los cambios son Observable, solo aquellos realizados en tus modelos en proceso, no aquellos hechos en tu almacén de datos desde otro proceso, como un widget o una extensión, o incluso otro contenedor de modelos dentro de tu app. Los cambios locales o internos se producen cuando tienes varios contextos de modelo que usan el mismo contenedor. Estos otros contextos de modelo pueden ver los cambios de los demás y, en el caso de Query, esos cambios se aplican automáticamente. Sin embargo, si estás aprovechando las API de búsqueda del contexto del modelo, los cambios realizados en otro contexto del modelo no se verán hasta que se active una nueva búsqueda.

    Una acción externa también puede modificar tus datos, como el guardado de un widget o la escritura tu contenedor del grupo de apps. Estos cambios actualizarán automáticamente por consultas. Sin embargo, para poder buscar los usos será necesario volver a buscarlos nuevamente. Volver a buscar puede ser complejo, en particular si no cambió nada de interés para el procesamiento del modelo.

    Por suerte, SwiftData tiene un historial que se mantuvo. Podemos saber qué modelos han cambiado, cuándo han cambiado, quién cambió los modelos e incluso qué propiedades se actualizaron. También usamos preservedValueOnDeletion en varias propiedades de Trip, así que cuando se borra un viaje, el historial tendrá una marca que podemos analizar para identificar el viaje borrado. Para obtener más información sobre el historial, consulta "Track Model Changes with SwiftData History" de WWDC24.

    Aprovechemos el historial persistente para saber si necesitamos volver a buscar. Lo primero que queremos hacer es obtener el último token de historial del contenedor. Podemos utilizar este token como nuestro marcador de dónde leímos en la base de datos. Muy parecido a un marcador que nos recuerda dónde dejamos nuestro libro. Configuramos una búsqueda con un descriptor de historial para la transacción predeterminada. Sin embargo, si tenemos mucho historial persistente, podríamos estar obteniendo una gran cantidad de datos, solo para tomar el último para nuestro token. Bueno, por suerte, una novedad en iOS 26 es la posibilidad de obtener el historial con sortBy. Podemos especificar cualquiera de las propiedades de transacción, como author o transactionIdentifier, como una clave para ordenar nuestro resultado del historial. Usemos el nuevo sortBy y configuremos el transactionIdentifier, pero al revés, para que las transacciones más recientes aparezcan primero. Solo nos importa la primera transacción que es la más nueva, así que limitemos nuestro resultado a 1. Y eso es todo lo que necesitamos hacer para obtener el token de historial y almacenarlo. Guardemos este token para más tarde y usémoslo en futuras búsquedas de historial. Cuando se produce un nuevo cambio, como la actualización de un viaje por parte del widget, se añade una nueva entrada a historial, y la app puede buscar en el historial para ver si hay algún cambio desde último token. Ahora que tenemos un token de historial almacenado, podemos crear un predicado que solo recupere el historial posterior a ese token. Y para encontrar solo los cambios que nos importan, construimos las entidades que queremos saber que han cambiado. Aquí solo queremos saber si ha cambiado el viaje o el alojamiento en caso de que el widget haya confirmado dónde nos alojamos. Y usar los nombres de las entidades en el predicado de cambios para filtrar los cambios del historial para nuestros tipos deseados. Con nuestro predicado de token y nuestro predicado de cambio, construimos predicado compuesto. Y con estos cambios implementados, cuando buscamos el historial, solo recuperamos el historial después de nuestro token y para las entidades que actualmente nos interesa procesar. Y con una mejor búsqueda del historial, podemos evitar tener que buscarlo si no hay cambios. Por suerte, el historial de SwiftData hace que sea fácil. Y así, sabemos cómo observar los cambios locales y remotos en nuestros modelos y datos. Espero que el video te haya sido útil y aproveches SwiftData para tus necesidades. Al crear tu gráfico de modelo, considera si la herencia es la opción adecuada y cuáles son las implicaciones de la migración a medida que gráfico evoluciona. Cuando se trata de obtener tus datos, crea buscadores y consultas más completos y eficaces. Y saber cuándo han cambiado tus datos puede ser valioso. La observación y el historial persistente tienen todo bajo control. Eso es todo por hoy. Espero que disfrutes tu viaje.

    • 1:07 - Import SwiftData and add @Model

      // Trip Models decorated with @Model
      import Foundation
      import SwiftData
      
      @Model
      class Trip {
        var name: String
        var destination: String
        var startDate: Date
        var endDate: Date
        
        var bucketList: [BucketListItem] = [BucketListItem]()
        var livingAccommodation: LivingAccommodation?
      }
      
      @Model
      class BucketListItem { ... }
      
      @Model
      class LivingAccommodation { ... }
    • 1:18 - Add modelContainer modifier

      // SampleTrip App using modelContainer Scene modifier
      
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .modelContainer(for: Trip.self)
        }
      }
    • 1:30 - Adopt @Query

      // Trip App using @Query
      import SwiftUI
      import SwiftData
      
      struct ContentView: View {
        @Query
        var trips: [Trip]
      
        var body: some View {
          NavigationSplitView {
            List(selection: $selection) {
              ForEach(trips) { trip in
                TripListItem(trip: trip)
              }
            }
          }
        }
      }
    • 3:28 - Add subclasses to Trip

      // Trip Model extended with two new subclasses
      
      @Model
      class Trip { 
        var name: String
        var destination: String
        var startDate: Date
        var endDate: Date
        
        var bucketList: [BucketListItem] = [BucketListItem]()
        var livingAccommodation: LivingAccommodation?
      }
      
      @available(iOS 26, *)
      @Model
      class BusinessTrip: Trip {
        var perdiem: Double = 0.0
      }
      
      @available(iOS 26, *)
      @Model
      class PersonalTrip: Trip {
        enum Reason: String, CaseIterable, Codable {
          case family
          case reunion
          case wellness
        }
        
        var reason: Reason
      }
    • 4:03 - Update modelContainer modifier

      // SampleTrip App using modelContainer Scene modifier
      
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .modelContainer(for: [Trip.self, BusinessTrip.self, PersonalTrip.self])
        }
      }
    • 7:06 - Add segmented control to drive a predicate to filter by Type

      // Trip App add segmented control
      import SwiftUI
      import SwiftData
      
      struct ContentView: View {
        @Query
        var trips: [Trip]
        
        enum Segment: String, CaseIterable {
          case all = "All"
          case personal = "Personal"
          case business = "Business"
        }
        
        init() {
          let classPredicate: Predicate<Trip>? = {
            switch segment.wrappedValue {
            case .personal:
              return #Predicate { $0 is PersonalTrip }
            case .business:
              return #Predicate { $0 is BusinessTrip }
            default:
              return nil
            }
          }
          _trips = Query(filter: classPredicate, sort: \.startDate, order: .forward)
        }
        
        var body: some View { ... }
      }
    • 8:26 - SampleTrips Versioned Schema 2.0

      enum SampleTripsSchemaV2: VersionedSchema {
        static var versionIdentifier: Schema.Version { Schema.Version(2, 0, 0) }
        static var models: [any PersistentModel.Type] {
          [SampleTripsSchemaV2.Trip.self, BucketListItem.self, LivingAccommodation.self]
        }
      
        @Model
        class Trip {
          @Attribute(.unique) var name: String
          var destination: String
      
          @Attribute(originalName: "start_date") var startDate: Date
          @Attribute(originalName: "end_date") var endDate: Date
          
          var bucketList: [BucketListItem]? = []
          var livingAccommodation: LivingAccommodation?
          
          ...
        }
      }
    • 8:41 - SampleTrips Custom Migration Stage from Version 1.0 to 2.0

      static let migrateV1toV2 = MigrationStage.custom(
         fromVersion: SampleTripsSchemaV1.self,
         toVersion: SampleTripsSchemaV2.self,
         willMigrate: { context in
            let fetchDesc =  FetchDescriptor<SampleTripsSchemaV1.Trip>()
            let trips = try? context.fetch(fetchDesc)
        
            // De-duplicate Trip instances here...
      
            try? context.save()
          }, 
          didMigrate: nil
      )
    • 9:09 - SampleTrips Versioned Schema 3.0

      enum SampleTripsSchemaV3: VersionedSchema {
        static var versionIdentifier: Schema.Version { Schema.Version(3, 0, 0) }
        static var models: [any PersistentModel.Type] {
          [SampleTripsSchemaV3.Trip.self, BucketListItem.self, LivingAccommodation.self]
        }
      
        @Model
        class Trip {
          #Unique<Trip>([\.name, \.startDate, \.endDate])
          #Index<Trip>([\.name], [\.startDate], [\.endDate], [\.name, \.startDate, \.endDate])
      
          @Attribute(.preserveValueOnDeletion)
          var name: String
          
          @Attribute(hashModifier:@"v3")
          var destination: String
      
          @Attribute(.preserveValueOnDeletion, originalName: "start_date")
          var startDate: Date
      
          @Attribute(.preserveValueOnDeletion, originalName: "end_date")
          var endDate: Date
        }
      }
    • 9:33 - SampleTrips Custom Migration Stage from Version 2.0 to 3.0

      static let migrateV2toV3 = MigrationStage.custom(
        fromVersion: SampleTripsSchemaV2.self,
        toVersion: SampleTripsSchemaV3.self,
        willMigrate: { context in
          let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV2.Trip>())
      
          // De-duplicate Trip instances here...
      
          try? context.save()
        }, 
        didMigrate: nil
      )
    • 9:50 - SampleTrips Versioned Schema 4.0

      @available(iOS 26, *)
      enum SampleTripsSchemaV4: VersionedSchema {
        static var versionIdentifier: Schema.Version { Schema.Version(4, 0, 0) }
      
        static var models: [any PersistentModel.Type] {
          [Trip.self, 
           BusinessTrip.self, 
           PersonalTrip.self, 
           BucketListItem.self,
           LivingAccommodation.self]
        }
      }
    • 10:03 - SampleTrips Lightweight Migration Stage from Version 3.0 to 4.0

      @available(iOS 26, *)
      static let migrateV3toV4 = MigrationStage.lightweight(
        fromVersion: SampleTripsSchemaV3.self,
        toVersion: SampleTripsSchemaV4.self
      )
    • 10:24 - SampleTrips Schema Migration Plan

      enum SampleTripsMigrationPlan: SchemaMigrationPlan {
        static var schemas: [any VersionedSchema.Type] {
          var currentSchemas: [any VersionedSchema.Type] =
            [SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self]
          if #available(iOS 26, *) {
            currentSchemas.append(SampleTripsSchemaV4.self)
          }
          return currentSchemas
        }
      
        static var stages: [MigrationStage] {
          var currentStages = [migrateV1toV2, migrateV2toV3]
          if #available(iOS 26, *) {
            currentStages.append(migrateV3toV4)
          }
          return currentStages
        }
      }
    • 10:51 - Use Schema Migration Plan with ModelContainer

      // SampleTrip App update modelContainer Scene modifier for migrated container
      
      @main
      struct TripsApp: App {
      
        let container: ModelContainer = {
          do {
            let schema = Schema(versionedSchema: SampleTripsSchemaV4.self)
            container = try ModelContainer(
              for: schema, migrationPlan: SampleTripsMigrationPlan.self)
          } catch { ... }
          return container
        }()
        var body: some Scene {
          WindowGroup {
            ContentView()
          }
          .modelContainer(container)
        }
      }
    • 11:48 - Add search predicate to Query

      // Trip App add search text to predicate
      struct ContentView: View {
        @Query
        var trips: [Trip]
      
        init( ... ) {
          let classPredicate: Predicate<Trip>? = {
            switch segment.wrappedValue {
            case .personal:
              return #Predicate { $0 is PersonalTrip }
            case .business:
              return #Predicate { $0 is BusinessTrip }
            default:
              return nil
            }
          }
          
          let searchPredicate = #Predicate<Trip> {
            searchText.isEmpty ? true : 
              $0.name.localizedStandardContains(searchText) ||              
              $0.destination.localizedStandardContains(searchText)
          }
          
          let fullPredicate: Predicate<Trip>
          if let classPredicate {
            fullPredicate = #Predicate { classPredicate.evaluate($0) &&
                                         searchPredicate.evaluate($0)}
          } else { 
            fullPredicate = searchPredicate
          }
          _trips = Query(filter: fullPredicate, sort: \.startDate, order: .forward)
        }
        var body: some View { ... }
      }
    • 12:31 - Tailor SwiftData Fetch in Custom Migration Stage

      static let migrateV1toV2 = MigrationStage.custom(
         fromVersion: SampleTripsSchemaV1.self,
         toVersion: SampleTripsSchemaV2.self,
         willMigrate: { context in
            var fetchDesc =  FetchDescriptor<SampleTripsSchemaV1.Trip>()
            fetchDesc.propertiesToFetch = [\.name]
      
            let trips = try? context.fetch(fetchDesc)
        
            // De-duplicate Trip instances here...
      
            try? context.save()
          }, 
          didMigrate: nil
      )
    • 13:11 - Add relationshipsToPrefetch in Custom Migration Stage

      static let migrateV1toV2 = MigrationStage.custom(
         fromVersion: SampleTripsSchemaV1.self,
         toVersion: SampleTripsSchemaV2.self,
         willMigrate: { context in
            var fetchDesc =  FetchDescriptor<SampleTripsSchemaV1.Trip>()
            fetchDesc.propertiesToFetch = [\.name]
            fetchDesc.relationshipKeyPathsForPrefetching = [\.livingAccommodation]
      
            let trips = try? context.fetch(fetchDesc)
        
            // De-duplicate Trip instances here...
      
            try? context.save()
          }, 
          didMigrate: nil
      )
    • 13:28 - Update Widget to harness fetchLimit

      // Widget code to get new Timeline Entry
      
      func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        let currentDate = Date.now
        var fetchDesc = FetchDescriptor(sortBy: [SortDescriptor(\Trip.startDate, order: .forward)])
        fetchDesc.predicate = #Predicate { $0.endDate >= currentDate }
      
        fetchDesc.fetchLimit = 1
        
        let modelContext = ModelContext(DataModel.shared.modelContainer)
        if let upcomingTrips = try? modelContext.fetch(fetchDesc) {
          if let trip = upcomingTrips.first { ... }
          
        }
      }
    • 16:24 - Fetch the last transaction efficiently

      // Fetch history with sortBy and fetchlimit to get the last token
      
      var historyDesc = HistoryDescriptor<DefaultHistoryTransaction>()
      historyDesc.sortBy = [.init(\.transactionIdentifier, order: .reverse)]
      historyDesc.fetchLimit = 1
      
      let transactions = try context.fetchHistory(historyDesc)
      if let transaction = transactions.last {
        historyToken = transaction.token
      }
    • 17:29 - Fetch History after the given token and only for the entities of concern

      // Changes AFTER the last known token
      let tokenPredicate = #Predicate<DefaultHistoryTransaction> { $0.token > historyToken }
      
      // Changes for ONLY entities of concern
      let entityNames = [LivingAccommodation.self, Trip.self]
      let changesPredicate = #Predicate<DefaultHistoryTransaction> {
                               $0.changes.contains { change in
                                 entityNames.contains(change.changedPersistentIdentifier.entityName)
                               }
                             }
      
      
      let fullPredicate = #Predicate<DefaultHistoryTransaction> {
                            tokenPredicate.evaluate($0)
                            &&
                            changesPredicate.evaluate($0)
                          }
      
      let historyDesc = HistoryDescriptor<DefaultHistoryTransaction>(predicate: fullPredicate)
      let transactions = try context.fetchHistory(historyDesc)
    • 0:00 - Introducción
    • SwiftData permite modelar y conservar datos de apps en todas las plataformas de Apple. Esta estructura simplifica la persistencia de datos, el modelado y la migración de esquemas, la administración de gráficos y la sincronización de CloudKit. La herencia de clases, una nueva funcionalidad disponible a partir de iOS 26, permite crear gráficos de modelos con la herencia.

    • 2:11 - Aprovecha la herencia de clases
    • La herencia de clases es una herramienta avanzada y particularmente útil cuando los modelos forman una jerarquía natural y comparten características comunes. La herencia permite la creación de subclases que heredan propiedades y comportamientos de una clase principal, lo que promueve la reutilización del código y mantiene una organización estructurada. En la app SampleTrips se aplica la herencia para modelar diferentes tipos de viajes, tanto personales como de negocios. Cada subclase hereda propiedades esenciales del modelo Trip y agrega atributos específicos relevantes a su subdominio. Este enfoque permite una representación de datos más personalizada y eficiente. Usa la herencia con prudencia. La herencia es apropiada cuando los modelos establecen una relación “is-a” y cuando las consultas involucran tanto a la clase principal como a sus subclases. Si los modelos solo comparten propiedades comunes sin una jerarquía natural, la conformidad con el protocolo es un enfoque más adecuado. La elección entre herencia y conformidad con el protocolo también depende de lo detalladas que sean las búsquedas en los datos.

    • 7:39 - Evolución de datos con la migración
    • El proceso de migración de datos de la app SampleTrips en diferentes versiones de iOS es un ejemplo de cómo garantizar la preservación de los datos del usuario durante las actualizaciones. El esquema de la app evolucionó a lo largo de varias versiones. En iOS 17 se introdujo SwiftData y la versión 2.0 del esquema, que hace que los nombres de los viajes sean únicos y se renombren las propiedades. En iOS 18 se agregó la versión 3.0, que utiliza índices y macros únicas, y conserva las propiedades al momento de la eliminación. Se usaron MigrationStages personalizados para la eliminación de duplicados. En iOS 26 se presenta la versión 4.0, que incluye subclases. Se necesita un MigrationStage ligero de la versión 3.0 a la 4.0. Un SchemaMigrationPlan se construye encapsulando los VersionedSchemas y MigrationStages en el orden correcto. Luego, se aplica SchemaMigrationPlan al crear ModelContainer para SampleTrips, lo que permite una migración sin problemas a través de todas las iteraciones anteriores y, al mismo tiempo, se preservan los datos del usuario.

    • 11:27 - Adaptación de los datos obtenidos
    • Para explorar la optimización de consultas y búsquedas, la app SampleTrips reintroduce la funcionalidad de la barra de búsqueda. Construye un predicado basado en la búsqueda del cliente y luego esta se combina con el predicado de clase para filtrar viajes. Estas técnicas mejoran el rendimiento de la búsqueda. Durante la migración, solo se obtienen las propiedades necesarias mediante propertiesToFetch. relationshipsToPrefetch se usa para optimizar el recorrido de relaciones. fetchLimit se configura en el código del widget para recuperar solo el viaje más reciente, lo que mejora la eficiencia.

    • 13:54 - Observación de cambios en los datos
    • La funcionalidad Observable de SwiftData te ayuda a reaccionar a los cambios locales realizados en PersistentModels. Pero no todos los cambios son observables. Los cambios de otros procesos, acciones externas o diferentes contextos de modelos dentro de la app requieren una nueva búsqueda, lo que puede resultar costoso. Para optimizar esto, puedes usar la funcionalidad de historial persistente de SwiftData. Al obtener el último token del historial y usarlo como marcador, puedes crear predicados para obtener únicamente las entradas del historial que se registraron después del último token y para entidades específicas de interés. Este enfoque permite que la app determine si es necesaria una nueva búsqueda, lo que evita la recuperación innecesaria de datos y mejora el rendimiento.

    • 18:28 - Próximos pasos
    • Al crear un gráfico de modelo, considera las implicaciones de herencia y migración. Mejora los buscadores de datos y las consultas para mejorar el rendimiento. Usa la observación y el historial persistente para llevar un registro de los cambios en los datos.

Developer Footer

  • Videos
  • WWDC25
  • SwiftData: Analiza a detalle la herencia y migración de esquemas
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines