axiom-core-data

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Core 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 configured
swift
// ✅ 正确:使用后台上下文处理繁重任务
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()
    }
}

// 如果已配置,变更会自动合并到viewContext

Async/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

删除规则

RuleBehaviorUse Case
NullifySet relationship to nilOptional relationships
CascadeDelete related objectsOwned children (User → Posts)
DenyPrevent deletion if related objects existProtect referenced data
No ActionDo 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 efficient
swift
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 = true

When 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:
  1. ✓ Test on REAL DEVICE (simulator deletes DB on rebuild)
  2. ✓ Install old version, create data
  3. ✓ Install new version over it
  4. ✓ Verify all data accessible
  5. ✓ Check migration performance (large datasets)
发布前必须完成:
  1. ✓ 在真实设备上测试(模拟器会在重建时删除数据库)
  2. ✓ 安装旧版本,创建数据
  3. ✓ 覆盖安装新版本
  4. ✓ 验证所有数据可访问
  5. ✓ 检查迁移性能(针对大数据集)

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 = true
swift
// ❌ 错误:无合并策略(冲突会导致崩溃)
let context = container.viewContext

// ✅ 正确:定义合并行为
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true

Performance Tips

性能优化技巧

  1. Use fetchBatchSize for large result sets
  2. Prefetch relationships that will be accessed
  3. Use background contexts for imports/exports
  4. Batch save — don't save after each insert
  5. Use fetchLimit when only first N results are needed
  6. Profile with SQL debug:
    -com.apple.CoreData.SQLDebug 1
  1. 使用fetchBatchSize处理大型结果集
  2. 预加载会被访问的关系
  3. 使用后台上下文处理导入/导出
  4. 批量保存 —— 不要在每次插入后都保存
  5. 当只需要前N条结果时使用fetchLimit
  6. 使用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

相关技能

  • axiom-core-data-diag
    — Debugging migrations, thread errors, N+1 queries
  • axiom-swiftdata
    — Modern alternative for iOS 17+
  • axiom-database-migration
    — Safe schema evolution patterns
  • axiom-swift-concurrency
    — Async/await patterns for Core Data
  • axiom-core-data-diag
    —— 调试迁移、线程错误、N+1查询
  • axiom-swiftdata
    —— iOS 17+的现代替代方案
  • axiom-database-migration
    —— 安全的架构演进模式
  • axiom-swift-concurrency
    —— Core Data的Async/await模式