关于 axiom-core-data
面向 Swift 开发者的 Core Data 实践参考,涵盖数据持久化、并发、关系建模和迁移。重点说明安全使用模式及常见陷阱,适用于旧版 iOS、进阶需求和现有 Core Data 项目。
主要功能
- 01Core Data 与 SwiftData 选型指导
- 02并发与后台上下文模式
- 03关系建模和数据抓取
- 04轻量迁移与映射模型
- 05性能建议和反模式提示
适用场景
- 01为旧版 iOS 应用选择持久化框架
- 02处理后台导入和异步数据抓取
- 03规划复杂实体关系与模式迁移
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-core-data安装后在 Claude Code 中输入/axiom-core-data确认已加载。
详细介绍
Core Data
概述
核心原则:Core Data 是成熟的对象图和持久化框架。当你需要 SwiftData 不支持的功能,或需要支持较旧的 iOS 版本时,请使用它。
何时使用 Core Data,何时使用 SwiftData:
- SwiftData(iOS 17+)——新应用,API 更简单,原生支持 Swift
- Core Data——iOS 16 及更早版本、高级功能、现有代码库
快速决策树
Which persistence framework?
├─ Targeting iOS 17+ only?
│ ├─ Simple data model? → SwiftData (recommended)
│ ├─ Need public CloudKit database? → Core Data (SwiftData is private-only)
│ ├─ Need custom migration logic? → Core Data (more control)
│ └─ Existing Core Data app? → Keep Core Data or migrate gradually
│
├─ Targeting iOS 16 or earlier?
│ └─ Core Data (SwiftData unavailable)
│
└─ Need both? → Use Core Data with SwiftData wrapper (advanced)
危险信号
如果出现以下任意情况,请立即停止:
- ❌ “在任意线程上访问托管对象”——违反线程约束
- ❌ “跳过在真机上的迁移测试”——模拟器会掩盖架构问题
- ❌ “到处使用单例上下文”——会导致并发崩溃
- ❌ “始终强制执行轻量级迁移”——复杂变更需要映射模型
- ❌ “在视图主体中执行提取”——请使用 @FetchRequest 或在视图模型中进行观察
Core Data 堆栈设置
现代堆栈(iOS 10+)
import CoreData
class CoreDataStack {
static let shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
// Configure for CloudKit if needed
// container.persistentStoreDescriptions.first?.cloudKitContainerOptions =
// NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.app")
container.loadPersistentStores { description, error in
if let error = error {
// Handle appropriately for production
fatalError("Failed to load store: \(error)")
}
}
// Enable automatic merging
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
var viewContext: NSManagedObjectContext {
persistentContainer.viewContext
}
func newBackgroundContext() -> NSManagedObjectContext {
persistentContainer.newBackgroundContext()
}
}
CloudKit 集成
import CoreData
class CloudKitStack {
lazy var container: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Model")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No store description")
}
// Enable CloudKit sync
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.yourapp"
)
// Enable history tracking for sync
description.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores { _, error in
if let error = error {
fatalError("CloudKit store failed: \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
}
并发模式
黄金法则
**绝不要跨线程传递 NSManagedObject。**请改为传递 objectID。
// ❌ WRONG: Passing object across threads
let user = viewContext.fetch(...) // Main thread
Task.detached {
print(user.name) // CRASH: Wrong thread
}
// ✅ CORRECT: Pass objectID, fetch on target context
let userID = user.objectID
Task.detached {
let bgContext = CoreDataStack.shared.newBackgroundContext()
let user = bgContext.object(with: userID) as! User
print(user.name) // Safe
}
后台处理
// ✅ CORRECT: Background context for heavy work
func importData(_ items: [ImportItem]) async throws {
let context = CoreDataStack.shared.newBackgroundContext()
try await context.perform {
for item in items {
let entity = Entity(context: context)
entity.configure(from: item)
}
try context.save()
}
}
// Changes automatically merge to viewContext if configured
Async/Await(iOS 15+)
// Modern async context operations
func fetchUsers() async throws -> [User] {
let context = CoreDataStack.shared.viewContext
return try await context.perform {
let request = User.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
return try context.fetch(request)
}
}
关系建模
一对多
// In User entity
@NSManaged var posts: NSSet?
// Convenience accessors
extension User {
var postsArray: [Post] {
(posts?.allObjects as? [Post]) ?? []
}
func addPost(_ post: Post) {
mutableSetValue(forKey: "posts").add(post)
}
}
多对多
// Both sides have NSSet
// User.tags <-> Tag.users
extension User {
func addTag(_ tag: Tag) {
mutableSetValue(forKey: "tags").add(tag)
// Core Data automatically adds to tag.users
}
}
删除规则
| 规则 | 行为 | 使用场景 |
|---|---|---|
| Nullify | 将关系设为 nil | 可选关系 |
| Cascade | 删除相关对象 | 所有权属于父对象的子对象(User → Posts) |
| Deny | 存在相关对象时阻止删除 | 保护被引用的数据 |
| No Action | 不执行任何操作(需要手动清理) | 很少适用 |
提取模式
SwiftUI 集成
struct UserList: View {
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \User.name, ascending: true)],
predicate: NSPredicate(format: "isActive == YES"),
animation: .default
)
private var users: FetchedResults<User>
var body: some View {
List(users) { user in
Text(user.name ?? "Unknown")
}
}
}
// Dynamic predicates
struct FilteredList: View {
@FetchRequest var items: FetchedResults<Item>
init(category: String) {
_items = FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.date, ascending: false)],
predicate: NSPredicate(format: "category == %@", category)
)
}
}
批量提取(避免 N+1)
// ❌ WRONG: N+1 queries
let users = try context.fetch(User.fetchRequest())
for user in users {
print(user.posts?.count ?? 0) // Fault fired for each user
}
// ✅ CORRECT: Prefetch relationships
let request = User.fetchRequest()
request.relationshipKeyPathsForPrefetching = ["posts"]
let users = try context.fetch(request)
for user in users {
print(user.posts?.count ?? 0) // Already loaded
}
大型数据集的批量大小
let request = User.fetchRequest()
request.fetchBatchSize = 20 // Load 20 at a time as needed
request.returnsObjectsAsFaults = true // Default, memory efficient
架构迁移
轻量级迁移(自动)
以下情况会自动处理:
- 添加可选属性
- 删除属性
- 重命名(使用重命名标识符)
- 添加带有可选值或默认值的关系
let description = NSPersistentStoreDescription()
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
何时需要映射模型
- 更改属性类型
- 拆分/合并实体
- 复杂的关系变更
- 迁移期间进行数据转换
// Create mapping model in Xcode:
// File → New → Mapping Model
// Select source and destination models
迁移测试清单
发布前必须完成:
- ✓ 在真机上测试(模拟器在重建时会删除数据库)
- ✓ 安装旧版本并创建数据
- ✓ 在旧版本上安装新版本
- ✓ 确认所有数据均可访问
- ✓ 检查迁移性能(大型数据集)
反模式
1. 所有操作都使用单例上下文
// ❌ WRONG: One context for all operations
class DataManager {
let context = CoreDataStack.shared.viewContext
func importInBackground() {
// Using main context on background = crash
for item in largeDataset {
let entity = Entity(context: context)
}
}
}
// ✅ CORRECT: Context per operation type
func importInBackground() {
let bgContext = CoreDataStack.shared.newBackgroundContext()
bgContext.perform {
// Safe background work
}
}
2. 在视图主体中执行提取
// ❌ WRONG: Fetch on every render
var body: some View {
let users = try? context.fetch(User.fetchRequest()) // Called repeatedly!
List(users ?? []) { ... }
}
// ✅ CORRECT: Use @FetchRequest
@FetchRequest(sortDescriptors: [])
var users: FetchedResults<User>
var body: some View {
List(users) { ... } // Automatic updates
}
3. 忽略合并策略
// ❌ WRONG: No merge policy (conflicts crash)
let context = container.viewContext
// ✅ CORRECT: Define merge behavior
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true
性能提示
- 大型结果集使用 fetchBatchSize
- 预提取将要访问的关系
- 导入/导出使用后台上下文
- 批量保存——不要每次插入后都保存
- 只需前 N 个结果时使用 fetchLimit
- 使用 SQL 调试进行性能分析:
-com.apple.CoreData.SQLDebug 1
高压场景
场景 1:“SwiftData 更简单,我们现在就迁移吧”
情况:有了新的 iOS 17 功能,产生了在项目进行到一半时迁移的想法。
风险:迁移很复杂。Core Data 和 SwiftData 混用会带来棘手的问题。
应对:“先完成当前里程碑。迁移需要专门安排时间并进行测试。”
场景 2:“跳过迁移测试吧,模拟器上能运行”
情况:只在模拟器中测试了架构变更。
风险:模拟器在重建时会删除数据库。真机会保留持久化数据,并可能崩溃。
应对:“必须在装有真实数据的真机上测试。现在花 15 分钟,就能避免生产环境崩溃。”
tvOS
在 tvOS 上使用 CoreData + CloudKit 很危险。 CloudKit 元数据会显著增加本地存储的占用空间,而 tvOS 没有持久的本地存储——系统可能随时删除缓存(包括 Application Support)。存储占用增加,再加上随机删除,是最糟糕的组合。
建议:对于 tvOS 数据持久化,请改用 SQLiteData 和 CloudKit SyncEngine。有关 tvOS 存储限制的完整信息,请参阅 axiom-tvos。
相关技能
axiom-core-data-diag— 调试迁移、线程错误和 N+1 查询axiom-swiftdata— iOS 17+ 的现代替代方案axiom-database-migration— 安全的架构演进模式axiom-swift-concurrency— Core Data 的 Async/await 模式