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

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Tópicos
  • Todos os vídeos
  • Sobre

Voltar para WWDC25

  • Sobre
  • Resumo
  • Transcrição
  • Código
  • SwiftData: Saiba mais sobre herança e migração de esquema

    Descubra como usar herança de classes para modelar seus dados. Saiba como otimizar consultas e migrar facilmente os dados do seu app para usar herança. Explore o uso de subclasses para construir gráficos de modelos, criar buscas e consultas eficientes e implementar migrações de esquema robustas. Entenda como usar Observable e histórico persistente para rastrear alterações de forma eficiente.

    Capítulos

    • 0:00 - Introdução
    • 2:11 - Usar a herança de classes
    • 7:39 - Preservar dados na migração
    • 11:27 - Otimizar os dados recuperados
    • 13:54 - Observar as alterações nos dados
    • 18:28 - Próximas etapas

    Recursos

    • Adopting SwiftData for a Core Data app
    • Building rich SwiftUI text experiences
    • SwiftData
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Novidades do SwiftUI

    WWDC24

    • Track model changes with SwiftData history
  • Buscar neste vídeo...

    Olá! Meu nome é Rishi Verma. Sou engineer na equipe SwiftData. Esta é a sessão “SwiftData: detalhes sobre herança e migração de esquema”. O SwiftData foi lançado no iOS 17 e permite modelar e gravar os dados do seu app em Swift em todas as plataformas da Apple. Ele permite escrever código rápido, eficiente e seguro usando os recursos modernos da linguagem Swift. E isso continua neste vídeo com uma introdução ao uso da herança de classes e ao entendimento de quando a herança é a escolha certa. Com a adoção da herança e a evolução do Schema, discutiremos as estratégias de migração usadas para preservar os dados. Depois, falaremos de algumas formas de personalizar buscas e consultas no SwiftData para obter o melhor desempenho. Por fim, veremos como observar mudanças nos modelos feitas local e remotamente. Já há algumas versões, temos usado um app familiar, o SampleTrips. É um app escrito em SwiftUI para acompanhar todas as diferentes viagens que planejei. Para usar o SwiftData com os modelos no app, preciso importar o framework e decorar cada modelo com a macro Model.

    Na definição do app, adicionamos o modificador modelContainer no WindowGroup, que informa toda a hierarquia de visualizações sobre o modelo Trip. Com o modelContainer configurado, posso atualizar minha view para usar a macro Query. Vamos remover os dados estáticos e preencher a view usando a macro Query, que vai gerar o código para buscar as viagens do contêiner de modelo.

    É isso. O app armazena as viagens que eu crio e se encaixa bem nas views da SwiftUI. O SwiftData oferece armazenamento, além de modelagem e migração do seu esquema, gerenciamento de grafos, sincronização com o CloudKit e muito mais. O recurso mais recente a chegar ao SwiftData é a herança de classes. O iOS 26 agora permite criar um grafo de modelos que aproveita a herança. A herança de classes é uma ferramenta poderosa. Vamos saber quando ela é a ferramenta certa para o trabalho. A herança funciona bem quando os modelos formam uma hierarquia natural e compartilham características. O modelo Trip tem destination, startDate e endDate, propriedades de que toda viagem precisa para sabermos para onde e quando vamos. Qualquer nova subclasse de Trip já terá essas propriedades e outros comportamentos compartilhados definidos em Trip. O modelo Trip também é um domínio amplo. Há muitos tipos de viagens que fazemos em nossas vidas. Uma subclasse de Trip deve ser um subdomínio natural que se encaixe no domínio mais amplo do Trip.

    No app de exemplo Trips, muitas viagens se encaixam em dois subdomínios naturais: viagens pessoais e de negócios. Com esses dois novos modelos expressando subdomínios naturais de uma viagem, quero adicionar propriedades e comportamentos específicos dessas subclasses. Para as viagens pessoais, vou adicionar uma enumeração que captura o motivo para estar fazendo essa viagem específica. Para a viagem de negócios, quero adicionar uma propriedade para registrar meu per diem, assim saberei quanto gastar na próxima aventura de trabalho. Vamos fazer isso no app de exemplo Trips e criar uma experiência melhor. Aqui está a classe Trip com as propriedades que queremos compartilhar com as subclasses. Vamos adicionar duas subclasses ao app Trips, uma para viagens de negócios e outra para viagens pessoais. Preciso decorá-las com @available no iOS 26 ou posterior para que estejam alinhadas com a compatibilidade com a herança do SwiftData. Vamos adicionar as propriedades específicas dos subdomínios às subclasses. Posso adicionar um per diem à BusinessTrip e definir seu valor inicial. Para PersonalTrip, adicionamos uma enumeração Reason para registrar o motivo para fazer a viagem pessoal. A última coisa que precisamos fazer é atualizar o esquema para incluir as novas subclasses. Vamos adicionar BusinessTrip e PersonalTrip ao modificador modelContainer e podemos começar. O app de exemplo Trips está pronto para aproveitar as viagens pessoais em azul e as de negócios em verde, sem nenhum código especial adicional. A herança de classes é uma ferramenta poderosa, mas não serve para todo tipo de problema. Vamos discutir quando você deve utilizar a herança.

    Há alguns cenários em que a herança é o caminho certo. Se os modelos expressam uma relação hierárquica e têm características comuns que você deseja estender, a herança pode ser a escolha correta, pois seus tipos formam uma relação “é-um”.

    Ao usar modelos herdados, uma viagem pessoal É-UM tipo de viagem, então sempre que trabalho com um tipo Trip, como na consulta de viagens nesta visualização, posso esperar encontrar todos os tipos de viagens, incluindo pessoais e de negócios, além de instâncias da própria classe principal Trip. Temos as viagens representadas por aviões com a mesma cor da interface, voando do contêiner de modelo para o contexto de modelo que possibilita a consulta. Entretanto, a herança não deve ser usada apenas para compartilhar características comuns entre modelos. Ao criar subclasses para todos os modelos com uma propriedade name, a hierarquia de classes teria muitos subdomínios que compartilham uma única propriedade para um propósito comum, enquanto todas as outras características estariam isoladas em seus próprios subdomínios. Como esses subdomínios não formam uma hierarquia natural, eles são expressos por meio da conformidade a um protocolo. A conformidade com protocolos permite que domínios distintos compartilhem comportamentos, mas não outras características não relacionadas. Outro motivo para usar herança depende de como você consulta ou busca modelos.

    Há várias formas de consultar dados, e estamos usando a macro Query para buscar todas as viagens do contêiner de modelo e direcionar as visualizações. Este é um exemplo de busca profunda.

    Se sempre buscarmos todas as viagens utilizando somente o tipo Trip, deveremos considerar PersonalTrip ou BusinessTrip como uma propriedade de Trip, em vez de uma subclasse. Se as consultas ou buscas recuperam apenas os tipos das classes folha, isso é conhecido como busca rasa. Nesse cenário, considere simplificar os modelos, já que Trip nunca é consultado ou utilizado como tipo. Se você usa buscas profundas e rasas, a herança vai ajudar, pois você vai pesquisar todas as viagens ou um subtipo específico, como PersonalTrips, para direcionar uma view personalizada para esse tipo. Vamos ver como podemos atualizar o app Trips para mostrar apenas as viagens pessoais ou de negócios.

    Vamos usar um controle segmentado para mostrar todas as viagens e as subclasses específicas.

    O segmento selecionado pode ser usado para criar um predicado que determina se a classe é de um tipo específico pela a palavra-chave “is”. Por exemplo, estou verificando se é uma PersonalTrip. Fornecemos o predicado e classificamos pela startDate para inicializar a consulta. Vamos conferir no app. Ele começa na visualização de viagens para eu ver todas as viagens. Posso restringir as visualizações para as subclasses específicas. Excelente! É assim que aproveitamos a herança de classes no iOS 26. Mas ainda não terminei. Fizemos mudanças importantes no nosso esquema e devemos considerar o que isso significa para o app e como migrar nossos dados. O app SampleTrips passou por várias evoluções nas últimas versões. Vamos registrar essas mudanças nos esquemas versionados e em um plano de migração de esquema, para que o app preserve os dados do usuário ao ser atualizado para a versão mais recente do app de exemplo Trips.

    Tudo começou com o primeiro vídeo, onde apresentamos o SwiftData no iOS 17 e usamos o Trips para guiar a adoção. Ao longo desses vídeos, aprendemos a tornar o nome de uma viagem único e como alterar o nome original de uma propriedade para preservar os dados durante a migração.

    Para o iOS 17, construímos nosso esquema versionado com um novo identificador de versão 2.0 e mostramos o modelo Trip modificado, com um nome único e as datas de início e término renomeadas.

    Adicionamos uma etapa de migração personalizada para remover duplicatas das Trips existentes. Aqui, usamos a função fetch do ModelContext para buscar todas as viagens, a fim de remover duplicatas.

    No iOS 18, usamos os macros Index e Unique, além de marcar quais propriedades seriam preservadas durante a exclusão.

    Isso permite identificar nossos modelos mesmo após a exclusão do repositório de dados.

    No esquema versionado para o iOS 18, marcamos a versão como 3 e registramos as mudanças no modelo Trip. Fizemos novo uso das macros Unique e Index para garantir que nossos dados sejam desduplicados e eficientes nas buscas e consultas. Também decoramos essas propriedades com preserveValueOnDeletion, para identificar a viagem excluída ao consumir o histórico persistente.

    Adicionamos outra etapa de migração personalizada para desduplicar as viagens ao migrar da versão 2 para a versão 3. No iOS 26, adicionaremos a versão 4 com subclasses e uma etapa de migração leve. Para o esquema da versão atual, marcamos como versão 4 e listamos os modelos do esquema, incluindo as novas subclasses. Como as subclasses foram decoradas com o iOS 26 ou posterior, o mesmo ocorre com o esquema de versão. Precisamos adicionar uma etapa de migração leve da versão 3 para a versão 4, com a mesma disponibilidade necessária antes. Com o esquema da versão final e a etapa de migração criados, podemos encapsular tudo isso em um plano de migração de esquema, para fornecer a ordem dos esquemas de versão e as etapas de migração a serem executadas. O plano de migração de esquema consiste em um conjunto de esquemas na ordem em que foram lançados. Quando o iOS 26 for lançado, vamos incluir o esquema mais recente com subclasses, seguido por uma sequência de etapas de migração para migrar de uma versão para a outra. É assim que criamos nosso plano de migração de esquema. Agora que criamos nosso esquema versionado e seu respectivo plano de migração, o próximo passo é utilizá-los ao criar o contêiner de modelo para o app de exemplo Trips. Vamos voltar ao modificador modelContainer e atualizá-lo para usar um contêiner de modelo com um plano de migração de esquema. Começamos adicionando uma nova propriedade container ao app, onde construiremos o esquema versionado com a versão 4 e forneceremos o plano de migração de esquema ao inicializador do ModelContainer. Vamos atualizar o modificador modelContainer para usar o novo contêiner migrável. Com tudo configurado, garantimos que as atualizações do app de exemplo Trips para utilizar herança possam migrar pelas diversas iterações lançadas anteriormente, preservando os dados do cliente.

    Agora que lidamos com a migração, vamos considerar onde melhorar as consultas e buscas utilizadas para direcionar nossas views e etapas de migração. Atualizamos a consulta com o segmento escolhido usando um predicado. Em um vídeo anterior, tínhamos uma barra de busca. Vamos adicionar de novo esse recurso e focar o processamento do texto de busca inserido pelo cliente. Começamos construindo um predicado com o searchText fornecido. Primeiro, verificamos se o texto está vazio. Se não estiver vazio, criamos um predicado composto para verificar se o nome ou o destino da viagem contém o texto fornecido. Depois, vamos criar um predicado composto combinando o predicado de busca com o de classe. Por fim, atualizamos o inicializador de consulta para usar o novo predicado composto. Com essa atualização, posso tocar na barra de busca, digitar um texto para filtrar as viagens e até refinar com um controle segmentado. Filtrar e classificar são algumas das formas de personalizar consultas e buscas. Vamos ver outras maneiras de personalizar as buscas no SwiftData. Está é a etapa de migração personalizada da versão 1 para a versão 2. Usaremos o bloco willMigrate para buscar todas as Trips. Porém, na minha lógica de desduplicação, acesso apenas a propriedade name, pois ela é única na versão 2, e a utilizo para garantir que não existam duplicatas. Como name é a única propriedade acessada, posso atualizar o fetchDescriptor para usar propertiesToFetch com o name, garantindo que nossos modelos Trip carreguem apenas os dados necessários durante a migração. Além disso, já podemos acessar uma relação específica, neste caso, vou reatribuir uma acomodação se encontrar um duplicado, podemos fazer o mesmo aprimoramento usando relationshipsToPrefetch. Vamos adicionar aqui a relação livingAccommodation.

    Agora que adotamos propriedades de prefetch, também podemos atualizar o código do widget no app de exemplo Trips para torná-lo mais eficiente. No widget do app de exemplo Trips, temos uma consulta para a viagem mais recente. No entanto, isso pode ser melhorado para buscar um único valor. Atualmente, o código do widget usa apenas o primeiro resultado da busca. Podemos tornar isso mais eficiente definindo um limite de busca.

    Ao definir o limite de busca, o widget obtém a primeira viagem que corresponda ao predicado e não precisará se preocupar com cenários em que eu tenha muitas viagens planejadas para o futuro. Com as consultas e buscas aprimoradas, vamos explorar como detectar quando o modelo foi alterado. Todos os modelos persistentes são Observable, então podemos usar o withObservationTracking para reagir a alterações nas propriedades de interesse dos nossos modelos. Para observar alterações nas datas de início e fim da viagem, podemos adicionar a função datesChangedAlert para que, se o usuário modificar as datas, um alerta seja exibido.

    Podemos observar alterações locais feitas em nossos PersistentModels dessa forma, e isso é muito útil para mudanças realizadas localmente. Para saber mais sobre as novidades em Observable, confira Novidades no Swift. Nem todas as alterações são Observable, apenas as feitas nos modelos dentro do mesmo processo, não as feitas no repositório de dados por outro processo, como um widget, extensão ou até mesmo outro contêiner de modelo no app. Alterações locais ou internas no app ocorrem quando você tem vários contextos de modelo usando o mesmo contêiner de modelo. Esses outros modelos de contato conseguem ver as alterações uns dos outros e, no caso de Query, essas mudanças são aplicadas automaticamente. Se você estiver usando as APIs de busca de contexto do modelo, as alterações feitas em outro contexto não serão vistas até que uma nova busca seja acionada.

    Uma ação externa pode modificar seus dados, como um widget ou outro app gravando no contêiner compartilhado via App Group. Essas alterações atualizarão a view com suporte da consulta. As buscas feitas com fetch precisarão ser feitas de novo. Refazer a busca pode ser custoso, especialmente se nada relevante para o processamento do nosso modelo tiver mudado.

    Felizmente, o SwiftData mantém um histórico armazenado. Posso saber quais modelos foram alterados, quando foram alterados, quem fez as mudanças e até quais propriedades foram atualizadas. Utilizamos o preservedValueOnDeletion em várias propriedades do Trip, assim, quando uma viagem é excluída, o histórico mantém um registro que pode ser analisado para identificar a viagem excluída. Para saber mais, confira “Monitorar alterações em modelos com o histórico do SwiftData”, da WWDC24.

    Vamos usar o histórico armazenado para saber se precisamos refazer a busca. A primeira coisa a fazer é buscar o token de histórico mais recente do contêiner. Assim, podemos usar esse token como marcador para indicar até onde lemos o banco de dados. É parecido com um marcador de página mostrando onde paramos de ler em nosso livro favorito. Configuramos uma busca no histórico utilizando um descriptor para DefaultHistoryTransaction. Se houver muito histórico armazenado, podemos acabar buscando muitos dados só para obter o último token. Uma novidade do iOS 26 é a capacidade de buscar o histórico com sortBy. Podemos especificar qualquer propriedade da transação, como author ou transactionIdentifier, como chave para classificar o resultado do histórico. Vamos adotar o novo sortBy e configurá-lo para transactionIdentifier, mas em ordem reversa, para as transações mais recentes aparecem primeiro. Como só nos interessa a primeira transação, que é a mais recente, vamos limitar o resultado a 1. É assim que buscamos e armazenamos o token de histórico mais recente. Vamos salvar esse token para usar nas próximas buscas de histórico. Quando uma nova alteração ocorrer, por exemplo, o Widget atualizar uma viagem, uma nova entrada será adicionada ao histórico e o app poderá buscar o histórico para verificar se houve mudanças desde o último token. Agora que temos um token de histórico armazenado, podemos criar um predicado que busque apenas o histórico posterior a esse token. Para encontrar só as mudanças que nos interessam, montamos a lista de entidades que queremos saber se foram alteradas. Queremos saber se uma viagem ou a acomodações foi modificada, caso o widget tenha confirmado onde estamos hospedados. Vamos usar os nomes das entidades no predicado de mudanças para filtrar as alterações no histórico para os tipos desejados. Por fim, com o predicado do token e o predicado de mudanças, criamos nosso predicado composto. Com essas alterações aplicadas, ao buscar o histórico, retornamos apenas os registros posteriores ao nosso token e referentes às entidades que temos interesse em processar. Com uma busca de histórico mais eficiente, podemos evitar a repetição quando não há mudanças relevantes. O histórico do SwiftData facilita esse processo. Com isso, sabemos como observar mudanças locais e remotas em nossos modelos e dados. Espero que você este vídeo tenha sido útil e que você possa usar o SwiftData para suas necessidades de armazenamento. Ao criar seu grafo de modelos, avalie se a herança é a abordagem adequada e quais serão as implicações de migração conforme seu grafo evolui. Na hora de obter seus dados, crie fetchers e queries mais vançados e eficientes. Saber quando seus dados foram alterados pode ser inestimável. A observação e o histórico armazenado cuidam disso para você. Isso é tudo. Boa viagem!

    • 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 - Introdução
    • O SwiftData permite a modelagem e a persistência de dados de apps em todas as plataformas da Apple. Esse framework simplifica a persistência de dados, a modelagem e migração de esquemas, o gerenciamento de gráficos e a sincronização do CloudKit. A herança de classes, um novo recurso disponível a partir do iOS 26, permite a criação de gráficos de modelo com herança.

    • 2:11 - Usar a herança de classes
    • A herança de classes é uma ferramenta poderosa e particularmente útil quando os modelos formam uma hierarquia natural e compartilham características comuns. A herança permite a criação de subclasses que herdam propriedades e comportamentos de uma classe principal, promovendo a reutilização de código e mantendo uma organização estruturada. O app SampleTrips aplica herança para modelar diferentes tipos de viagens, como viagens pessoais e de negócios. Cada subclasse herda propriedades essenciais do modelo Trip e adiciona atributos específicos relevantes para o subdomínio. Essa abordagem permite uma representação mais personalizada e eficiente dos dados. Use a herança de maneira criteriosa. A herança é apropriada quando os modelos estabelecem uma relação "is-a" e quando as consultas envolvem a classe principal e suas subclasses. Se os modelos compartilham apenas propriedades comuns sem uma hierarquia natural, a conformidade de protocolo é mais adequada. A escolha entre herança e conformidade de protocolo também depende da profundidade das pesquisas realizadas nos dados.

    • 7:39 - Preservar dados na migração
    • O processo de migração de dados do app SampleTrips em versões do iOS é um exemplo de como garantir a preservação dos dados do usuário durante as atualizações. O esquema do app evoluiu ao longo de várias versões: o iOS 17 introduziu o SwiftData e a versão 2.0 do esquema, tornando os nomes de viagens únicos e renomeando propriedades. O iOS 18 adicionou a versão 3.0, utilizando índice e macros exclusivas e preservando as propriedades na exclusão. MigrationStages personalizados foram usados para eliminar duplicações. O iOS 26 apresenta a versão 4.0, que inclui subclasses. Um MigrationStage leve é necessário da versão 3.0 para a 4.0. Um SchemaMigrationPlan é construído encapsulando os VersionedSchemas e MigrationStages na ordem correta. O SchemaMigrationPlan é então aplicado ao criar o ModelContainer para o SampleTrips, permitindo migração perfeita em todas as iterações anteriores, preservando os dados do usuário.

    • 11:27 - Otimizar os dados recuperados
    • Para explorar a otimização de consultas e buscas, o app SampleTrips reintroduz a funcionalidade da barra de pesquisa. O app constrói um predicado com base na pesquisa do cliente e, em seguida, é combinado com o predicado da classe para filtrar viagens. Além da pesquisa, essas técnicas aprimoram o desempenho da busca: durante a migração, somente as propriedades necessárias são buscadas usando propertiesToFetch. relationshipsToPrefetch é utilizado para otimizar a transição de relacionamentos. O fetchLimit é definido no código do widget para recuperar apenas a única viagem mais recente, melhorando a eficiência.

    • 13:54 - Observar as alterações nos dados
    • O recurso Observable do SwiftData ajuda a reagir às alterações locais feitas em PersistentModels. No entanto, nem todas as mudanças são observáveis. Mudanças de outros processos, ações externas ou diferentes contextos do modelo dentro do app exigem nova busca, o que pode sair caro. Para otimizar as novas buscas, você pode usar o recurso de histórico persistente do SwiftData. Ao buscar o token de histórico mais recente e usá-lo como marcador, você pode criar predicados para buscar apenas entradas que ocorreram após o último token e para entidades de interesse específicas. Essa abordagem permite que o app determine se uma nova busca é necessária, evitando a recuperação desnecessária de dados e melhorando o desempenho.

    • 18:28 - Próximas etapas
    • Ao criar um gráfico de modelo, considere as implicações de herança e migração. Aprimore as buscas de dados e as consultas para obter desempenho. Utilize observação e histórico persistente para monitorar alterações nos dados.

Developer Footer

  • Vídeos
  • WWDC25
  • SwiftData: Saiba mais sobre herança e migração de esquema
  • 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