axiom-core-data
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCore Data
Core Data
Overview
概述
Core principle: Core Data is a mature object graph and persistence framework. Use it when needing features SwiftData doesn't support, or when targeting older iOS versions.
When to use Core Data vs SwiftData:
- SwiftData (iOS 17+) — New apps, simpler API, Swift-native
- Core Data — iOS 16 and earlier, advanced features, existing codebases
核心原则:Core Data是一个成熟的对象图和持久化框架。当需要SwiftData不支持的功能,或针对旧版iOS系统时使用它。
何时选择Core Data vs SwiftData:
- SwiftData (iOS 17+) —— 新应用、更简洁的API、原生Swift支持
- Core Data —— iOS 16及更早版本、高级功能、现有代码库
Quick Decision Tree
快速决策树
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)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)Red Flags
红色警示
If ANY of these appear, STOP:
- ❌ "Access managed objects on any thread" — Thread-confinement violation
- ❌ "Skip migration testing on real device" — Simulator hides schema issues
- ❌ "Use a singleton context everywhere" — Leads to concurrency crashes
- ❌ "Force lightweight migration always" — Complex changes need mapping models
- ❌ "Fetch in view body" — Use @FetchRequest or observe in view model
如果出现以下任何一种情况,请立即停止:
- ❌ "在任意线程访问托管对象" —— 违反线程约束
- ❌ "跳过在真实设备上的迁移测试" —— 模拟器会隐藏架构问题
- ❌ "在所有地方使用单例上下文" —— 会导致并发崩溃
- ❌ "始终强制使用轻量迁移" —— 复杂变更需要映射模型
- ❌ "在视图体内执行查询" —— 使用@FetchRequest或在视图模型中观察
Core Data Stack Setup
Core Data栈搭建
Modern Stack (iOS 10+)
现代栈(iOS 10+)
swift
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()
}
}swift
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 Integration
CloudKit集成
swift
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
}()
}swift
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
}()
}Concurrency Patterns
并发模式
The Golden Rule
黄金法则
NEVER pass NSManagedObject across threads. Pass objectID instead.
swift
// ❌ 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
}绝对不要跨线程传递NSManagedObject。应传递objectID替代。
swift
// ❌ 错误:跨线程传递对象
let user = viewContext.fetch(...) // 主线程
Task.detached {
print(user.name) // 崩溃:线程错误
}
// ✅ 正确:传递objectID,在目标上下文查询
let userID = user.objectID
Task.detached {
let bgContext = CoreDataStack.shared.newBackgroundContext()
let user = bgContext.object(with: userID) as! User
print(user.name) // 安全
}Background Processing
后台处理
swift
// ✅ 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 configuredswift
// ✅ 正确:使用后台上下文处理繁重任务
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()
}
}
// 如果已配置,变更会自动合并到viewContextAsync/Await (iOS 15+)
Async/Await(iOS 15+)
swift
// 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)
}
}swift
// 现代异步上下文操作
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)
}
}Relationship Modeling
关系建模
One-to-Many
一对多
swift
// 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)
}
}swift
// 在User实体中
@NSManaged var posts: NSSet?
// 便捷访问器
extension User {
var postsArray: [Post] {
(posts?.allObjects as? [Post]) ?? []
}
func addPost(_ post: Post) {
mutableSetValue(forKey: "posts").add(post)
}
}Many-to-Many
多对多
swift
// 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
}
}swift
// 双方都使用NSSet
// User.tags <-> Tag.users
extension User {
func addTag(_ tag: Tag) {
mutableSetValue(forKey: "tags").add(tag)
// Core Data会自动添加到tag.users
}
}Delete Rules
删除规则
| Rule | Behavior | Use Case |
|---|---|---|
| Nullify | Set relationship to nil | Optional relationships |
| Cascade | Delete related objects | Owned children (User → Posts) |
| Deny | Prevent deletion if related objects exist | Protect referenced data |
| No Action | Do nothing (manual cleanup required) | Rarely appropriate |
| 规则 | 行为 | 使用场景 |
|---|---|---|
| Nullify | 将关系设置为nil | 可选关系 |
| Cascade | 删除关联对象 | 所属子对象(User → Posts) |
| Deny | 如果存在关联对象则阻止删除 | 保护引用数据 |
| No Action | 不做任何操作(需手动清理) | 极少适用 |
Fetching Patterns
查询模式
SwiftUI Integration
SwiftUI集成
swift
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)
)
}
}swift
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")
}
}
}
// 动态谓词
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)
)
}
}Batch Fetching (Avoid N+1)
批量查询(避免N+1问题)
swift
// ❌ 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
}swift
// ❌ 错误:N+1查询
let users = try context.fetch(User.fetchRequest())
for user in users {
print(user.posts?.count ?? 0) // 每个用户都会触发错误
}
// ✅ 正确:预加载关系
let request = User.fetchRequest()
request.relationshipKeyPathsForPrefetching = ["posts"]
let users = try context.fetch(request)
for user in users {
print(user.posts?.count ?? 0) // 已预加载
}Batch Size for Large Datasets
大数据集的批量大小设置
swift
let request = User.fetchRequest()
request.fetchBatchSize = 20 // Load 20 at a time as needed
request.returnsObjectsAsFaults = true // Default, memory efficientswift
let request = User.fetchRequest()
request.fetchBatchSize = 20 // 根据需要每次加载20条
request.returnsObjectsAsFaults = true // 默认设置,节省内存Schema Migration
架构迁移
Lightweight Migration (Automatic)
轻量迁移(自动)
Handled automatically for:
- Adding optional attributes
- Removing attributes
- Renaming (with renaming identifier)
- Adding relationships with optional or default value
swift
let description = NSPersistentStoreDescription()
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true以下情况会自动处理:
- 添加可选属性
- 删除属性
- 重命名(需设置重命名标识符)
- 添加带可选值或默认值的关系
swift
let description = NSPersistentStoreDescription()
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = trueWhen Mapping Model Is Needed
何时需要映射模型
- Changing attribute types
- Splitting/merging entities
- Complex relationship changes
- Data transformation during migration
swift
// Create mapping model in Xcode:
// File → New → Mapping Model
// Select source and destination models- 更改属性类型
- 拆分/合并实体
- 复杂关系变更
- 迁移期间的数据转换
swift
// 在Xcode中创建映射模型:
// 文件 → 新建 → 映射模型
// 选择源模型和目标模型Migration Testing Checklist
迁移测试清单
MANDATORY before shipping:
- ✓ Test on REAL DEVICE (simulator deletes DB on rebuild)
- ✓ Install old version, create data
- ✓ Install new version over it
- ✓ Verify all data accessible
- ✓ Check migration performance (large datasets)
发布前必须完成:
- ✓ 在真实设备上测试(模拟器会在重建时删除数据库)
- ✓ 安装旧版本,创建数据
- ✓ 覆盖安装新版本
- ✓ 验证所有数据可访问
- ✓ 检查迁移性能(针对大数据集)
Anti-Patterns
反模式
1. Singleton Context for Everything
1. 所有操作都使用单例上下文
swift
// ❌ 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
}
}swift
// ❌ 错误:一个上下文用于所有操作
class DataManager {
let context = CoreDataStack.shared.viewContext
func importInBackground() {
// 在后台使用主上下文 = 崩溃
for item in largeDataset {
let entity = Entity(context: context)
}
}
}
// ✅ 正确:根据操作类型使用不同上下文
func importInBackground() {
let bgContext = CoreDataStack.shared.newBackgroundContext()
bgContext.perform {
// 安全的后台操作
}
}2. Fetching in View Body
2. 在视图体内执行查询
swift
// ❌ 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
}swift
// ❌ 错误:每次渲染都执行查询
var body: some View {
let users = try? context.fetch(User.fetchRequest()) // 会被反复调用!
List(users ?? []) { ... }
}
// ✅ 正确:使用@FetchRequest
@FetchRequest(sortDescriptors: [])
var users: FetchedResults<User>
var body: some View {
List(users) { ... } // 自动更新
}3. Ignoring Merge Policy
3. 忽略合并策略
swift
// ❌ WRONG: No merge policy (conflicts crash)
let context = container.viewContext
// ✅ CORRECT: Define merge behavior
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = trueswift
// ❌ 错误:无合并策略(冲突会导致崩溃)
let context = container.viewContext
// ✅ 正确:定义合并行为
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = truePerformance Tips
性能优化技巧
- Use fetchBatchSize for large result sets
- Prefetch relationships that will be accessed
- Use background contexts for imports/exports
- Batch save — don't save after each insert
- Use fetchLimit when only first N results are needed
- Profile with SQL debug:
-com.apple.CoreData.SQLDebug 1
- 使用fetchBatchSize处理大型结果集
- 预加载会被访问的关系
- 使用后台上下文处理导入/导出
- 批量保存 —— 不要在每次插入后都保存
- 当只需要前N条结果时使用fetchLimit
- 使用SQL调试分析:
-com.apple.CoreData.SQLDebug 1
Pressure Scenarios
常见场景应对
Scenario 1: "SwiftData is simpler, let's migrate now"
场景1:"SwiftData更简单,我们现在就迁移"
Situation: New iOS 17 features available, temptation to migrate mid-project.
Risk: Migration is complex. Mixed Core Data + SwiftData has sharp edges.
Response: "Complete current milestone first. Migration needs dedicated time and testing."
情况:iOS 17新功能可用,项目中途有迁移的冲动。
风险:迁移过程复杂。混合使用Core Data + SwiftData存在很多陷阱。
应对:"先完成当前里程碑。迁移需要专门的时间和测试。"
Scenario 2: "Skip migration testing, simulator works"
场景2:"跳过迁移测试,模拟器运行正常"
Situation: Schema change tested only in simulator.
Risk: Simulator deletes database on rebuild. Real devices keep persistent data and crash.
Response: "MANDATORY: Test on real device with real data. 15 minutes now prevents production crash."
情况:仅在模拟器中测试了架构变更。
风险:模拟器会在重建时删除数据库。真实设备会保留持久化数据,可能导致崩溃。
应对:"必须在真实设备上使用真实数据测试。现在花15分钟可避免生产环境崩溃。"
Related Skills
相关技能
- — Debugging migrations, thread errors, N+1 queries
axiom-core-data-diag - — Modern alternative for iOS 17+
axiom-swiftdata - — Safe schema evolution patterns
axiom-database-migration - — Async/await patterns for Core Data
axiom-swift-concurrency
- —— 调试迁移、线程错误、N+1查询
axiom-core-data-diag - —— iOS 17+的现代替代方案
axiom-swiftdata - —— 安全的架构演进模式
axiom-database-migration - —— Core Data的Async/await模式
axiom-swift-concurrency