Highlight
Core Data 在 iOS 17 引入了 Composite Attributes(组合属性)来组织结构化数据、Staged Migration(分阶段迁移)来处理复杂模型变更、以及 Deferred Migration(延迟迁移)来避免启动时的长时间等待。
核心内容
数据模型随着 app 迭代会变得越来越复杂。以前遇到这些问题时,开发者往往要在”写一堆迁移代码”和”放弃 Core Data 换用其他方案”之间做选择。
Composite Attributes 解决的是结构化数据的存储问题。以前要把一组相关属性拍平成多个独立字段,或者用 Transformable 类型自己写序列化代码。Composite Attributes 让你可以把一组相关属性封装成一个组合属性,像字典一样访问,还能直接用 keypath 做查询条件。
Staged Migration 解决的是复杂模型变更的迁移问题。有些变更(比如把一个实体的属性拆成独立实体)超出了轻量级迁移的能力范围。以前只能写自定义迁移映射,代码量大且容易出错。Staged Migration 让你把复杂变更拆成多个轻量级迁移可以处理的步骤,Core Data 自动按顺序执行。
Deferred Migration 解决的是迁移阻塞 UI 的问题。有些轻量级迁移虽然能自动完成,但清理旧数据需要时间。以前这会在 app 启动时卡住。Deferred Migration 让清理工作延迟到后台执行,app 先用上新 schema,数据清理稍后再做。
详细内容
Composite Attributes
(00:56)Composite Attributes 是 Core Data 的新属性类型,用于封装结构化数据。
假设有一个 Aircraft 实体,以前用 Transformable 类型存储颜色方案:
@NSManaged public var colors: Data? // Transformable,需要自定义 transformer
现在可以用 Composite Attribute:
@NSManaged public var colorScheme: [String: Any]?
在 Xcode 模型编辑器中定义 composite attribute:
- 添加 composite attribute,命名为
colorScheme - 在其中添加三个 String 子属性:
primary、secondary、tertiary - 在
Aircraft实体中添加colorScheme属性,类型设为colorScheme
设置 composite attribute:
private func addAircraft() {
viewContext.performAndWait {
let newAircraft = Aircraft(context: viewContext)
newAircraft.tailNumber = tailNumber
newAircraft.aircraftType = aircraftType
newAircraft.aircraftClass = aircraftClass
newAircraft.aircraftCategory = aircraftCategory
newAircraft.colorScheme = [
"primary": primaryColor.rawValue,
"secondary": secondaryColor.rawValue,
"tertiary": tertiaryColor.rawValue
]
do {
try viewContext.save()
} catch {
// 处理错误
}
}
}
关键点:
- 用字典语法访问 composite attribute 的子属性
- 键名与模型编辑器中定义的属性名一致
用 keypath 查询 composite attribute:
private func findAircraft(with color: String) {
viewContext.performAndWait {
let fetchRequest = Aircraft.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "colorScheme.primary == %@", color
)
do {
let fetchedResults = try viewContext.fetch(fetchRequest)
// ...
} catch {
// 处理错误
}
}
}
关键点:
- 使用
colorScheme.primary这样的 namespaced keypath - 可以直接在
NSPredicate中查询子属性 - 这是 Transformable 属性做不到的
Composite Attributes 还可以嵌套,一个 composite attribute 可以包含其他 composite attribute。元素只能是 NSAttributeDescription 或嵌套的 NSCompositeAttributeDescription,不能包含 relationship。
性能方面,如果 fetching 一个实体后几乎总是需要访问它的某个 relationship,可以把那个 relationship 的数据改成 composite attribute。这样可以避免 faulting 带来的额外查询。
Staged Migration
(07:36)当模型变更超出轻量级迁移能力时,使用 Staged Migration。
核心思路:把复杂的非轻量级变更,拆成多个轻量级迁移可以处理的步骤。
示例场景:Aircraft 实体有一个 flightData 二进制属性,需要把它拆成独立的 FlightData 实体。
分解步骤:
- ModelV1(原始):
Aircraft有flightData属性 - ModelV2:添加
FlightData实体,与Aircraft建立 relationship,轻量级迁移可以完成 - ModelV3:删除
Aircraft的flightData属性,数据已通过自定义代码迁移到FlightData
创建模型引用:
let v1ModelChecksum = "kk8XL4OkE7gYLFHTrH6W+EhTw8w14uq1klkVRPiuiAk="
let v1ModelReference = NSManagedObjectModelReference(
modelName: "modelV1",
in: Bundle.main,
versionChecksum: v1ModelChecksum
)
let v2ModelChecksum = "PA0Gbxs46liWKg7/aZMCBtu9vVIF6MlskbhhjrCd7ms="
let v2ModelReference = NSManagedObjectModelReference(
modelName: "modelV2",
in: Bundle.main,
versionChecksum: v2ModelChecksum
)
let v3ModelChecksum = "iWKg7bxs46g7liWkk8XL4OkE7gYL/FHTrH6WF23Jhhs="
let v3ModelReference = NSManagedObjectModelReference(
modelName: "modelV3",
in: Bundle.main,
versionChecksum: v3ModelChecksum
)
关键点:
versionChecksum用NSManagedObjectModel.versionChecksum获取- 也可以在 Xcode build log 中搜索 “version checksum”
- versioned model 的 checksum 也在
VersionInfo.plist中
创建迁移阶段:
// V1 → V2 是轻量级变更(添加属性/实体)
let lightweightStage = NSLightweightMigrationStage([v1ModelChecksum])
lightweightStage.label = "V1 to V2: Add flightData attribute"
// V2 → V3 需要自定义代码迁移数据
let customStage = NSCustomMigrationStage(
migratingFrom: v2ModelReference,
to: v3ModelReference
)
customStage.label = "V2 to V3: Denormalize model with FlightData entity"
在自定义阶段中迁移数据:
customStage.willMigrateHandler = { migrationManager, currentStage in
guard let container = migrationManager.container else {
return
}
let context = container.newBackgroundContext()
try context.performAndWait {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Aircraft")
fetchRequest.predicate = NSPredicate(format: "flightData != nil")
do {
let fetchedResults = try context.fetch(fetchRequest)
for airplane in fetchedResults {
guard let airplane = airplane as? NSManagedObject else { continue }
let fdEntity = NSEntityDescription.insertNewObject(
forEntityName: "FlightData",
into: context
)
let flightData = airplane.value(forKey: "flightData")
fdEntity.setValue(flightData, forKey: "data")
fdEntity.setValue(airplane, forKey: "aircraft")
airplane.setValue(nil, forKey: "flightData")
}
try context.save()
} catch {
// 处理错误
}
}
}
关键点:
- 使用
NSFetchRequestResult和NSManagedObject泛型,因为迁移时子类可能不存在 willMigrateHandler在轻量级迁移完成后、schema 更新前执行- 在 handler 中创建新实体、复制数据、建立 relationship
didMigrateHandler在 schema 更新后执行(如果需要)
配置 staged migration:
let migrationStages = [lightweightStage, customStage]
let migrationManager = NSStagedMigrationManager(migrationStages)
let persistentContainer = NSPersistentContainer(
path: "/path/to/store.sqlite",
managedObjectModel: myModel
)
var storeDescription = persistentContainer.persistentStoreDescriptions.first
storeDescription?.setOption(
migrationManager,
forKey: NSPersistentStoreStagedMigrationManagerOptionKey
)
persistentContainer.loadPersistentStores { storeDescription, error in
if let error = error {
// 处理错误
}
}
关键点:
NSStagedMigrationManager管理整个迁移流程- 通过
NSPersistentStoreStagedMigrationManagerOptionKey添加到 store options - Core Data 自动按顺序执行每个阶段
Deferred Migration
(18:56)某些轻量级迁移的清理工作可以延迟执行。
启用延迟迁移:
let options = [
NSPersistentStoreDeferredLightweightMigrationOptionKey: true,
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
let store = try coordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: options
)
在合适的时机完成延迟迁移:
// 通过 BGProcessingTask 在后台执行
let metadata = coordinator.metadata(for: store)
if metadata[NSPersistentStoreDeferredLightweightMigrationOptionKey] == true {
coordinator.finishDeferredLightweightMigration()
}
关键点:
- 轻量级迁移本身仍是同步的,只有清理工作被延迟
- app 可以立即使用新 schema
- 延迟迁移支持回退到 macOS Big Sur 和 iOS 14
- 仅支持 SQLite store type
- 适合的场景:删除属性/relationship、改变 entity hierarchy、改变 ordered relationship
- 建议用
BGProcessingTask在设备空闲时执行
Staged + Deferred 组合使用
(22:08)复杂迁移可以组合两种技术。比如示例中的 ModelV3 删除 flightData 属性,这个清理工作可以延迟到后台执行。
核心启发
1. 地址/联系人结构化存储
- 做什么:用 Composite Attributes 把地址的省、市、区、街道封装成一个
address属性 - 为什么值得做:比拍平成四个独立属性更直观,查询时可以用
address.city == %@直接过滤 - 怎么开始:在 Xcode 模型编辑器中创建 composite attribute,包含
province、city、district、street四个 String 子属性
2. 安全迁移大型用户数据库
- 做什么:用 Staged Migration 把复杂的数据库变更拆成多步,避免一次性迁移导致启动崩溃
- 为什么值得做:用户数据量大时,复杂迁移可能超时被系统杀死,分阶段执行更稳定
- 怎么开始:分析模型变更,把非轻量级变更拆成”添加新结构→迁移数据→删除旧结构”三步,每步用一个 model version
3. 零阻塞启动体验
- 做什么:用 Deferred Migration 把清理工作放到后台,app 启动后立即可用
- 为什么值得做:用户打开 app 时如果卡在启动屏超过几秒就会流失,延迟迁移让启动保持快速
- 怎么开始:在 store options 中加入
NSPersistentStoreDeferredLightweightMigrationOptionKey: true,注册BGProcessingTask在后台调用finishDeferredLightweightMigration()
4. 从 Transformable 迁移到 Composite Attributes
- 做什么:把现有的 Transformable 属性(如存储 JSON 编码的自定义类型)逐步替换为 Composite Attributes
- 为什么值得做:Transformable 无法直接查询子字段,Composite Attributes 支持
NSPredicatekeypath 查询,性能更好 - 怎么开始:创建新的 composite attribute,在 Staged Migration 的
willMigrateHandler中把 Transformable 数据解析并写入 composite 子属性
关联 Session
- Meet SwiftData — Apple 新推出的数据持久化框架
- SwiftData 深入 — SwiftData 高级用法和与 Core Data 的关系
- Evolve your app’s schema — 数据模型演进的最佳实践
评论
GitHub Issues · utterances