-
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
Vídeos relacionados
WWDC25
WWDC24
-
Buscar neste vídeo...
-
-
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.