mcpField
A
axiom-core-data

axiom-core-data

github.com/charleswiltgen开发1.2k170 次安装今天更新
代码仓库

关于 axiom-core-data

面向 Swift 开发者的 Core Data 实践参考,涵盖数据持久化、并发、关系建模和迁移。重点说明安全使用模式及常见陷阱,适用于旧版 iOS、进阶需求和现有 Core Data 项目。

主要功能

  1. 01Core Data 与 SwiftData 选型指导
  2. 02并发与后台上下文模式
  3. 03关系建模和数据抓取
  4. 04轻量迁移与映射模型
  5. 05性能建议和反模式提示

适用场景

  1. 01为旧版 iOS 应用选择持久化框架
  2. 02处理后台导入和异步数据抓取
  3. 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. ✓ 在真机上测试(模拟器在重建时会删除数据库)
  2. ✓ 安装旧版本并创建数据
  3. ✓ 在旧版本上安装新版本
  4. ✓ 确认所有数据均可访问
  5. ✓ 检查迁移性能(大型数据集)

反模式

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

性能提示

  1. 大型结果集使用 fetchBatchSize
  2. 预提取将要访问的关系
  3. 导入/导出使用后台上下文
  4. 批量保存——不要每次插入后都保存
  5. 只需前 N 个结果时使用 fetchLimit
  6. 使用 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 模式

评论

支持 Markdown