axiom-core-data by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-core-data核心原则:Core Data 是一个成熟的对象图和持久化框架。当需要 SwiftData 不支持的功能,或者需要支持较旧的 iOS 版本时使用它。
何时使用 Core Data 与 SwiftData:
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)
如果出现以下任何情况,请立即停止:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import CoreData
class CoreDataStack {
static let shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
// 如果需要,配置 CloudKit
// container.persistentStoreDescriptions.first?.cloudKitContainerOptions =
// NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.app")
container.loadPersistentStores { description, error in
if let error = error {
// 针对生产环境进行适当处理
fatalError("Failed to load store: \(error)")
}
}
// 启用自动合并
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
var viewContext: NSManagedObjectContext {
persistentContainer.viewContext
}
func newBackgroundContext() -> NSManagedObjectContext {
persistentContainer.newBackgroundContext()
}
}
import CoreData
class CloudKitStack {
lazy var container: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Model")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No store description")
}
// 启用 CloudKit 同步
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.yourapp"
)
// 为同步启用历史记录跟踪
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。
// ❌ 错误:跨线程传递对象
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) // 安全
}
// ✅ 正确:使用后台上下文进行繁重工作
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
// 现代异步上下文操作
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)
}
}
// 在 User 实体中
@NSManaged var posts: NSSet?
// 便捷访问器
extension User {
var postsArray: [Post] {
(posts?.allObjects as? [Post]) ?? []
}
func addPost(_ post: Post) {
mutableSetValue(forKey: "posts").add(post)
}
}
// 双方都有 NSSet
// User.tags <-> Tag.users
extension User {
func addTag(_ tag: Tag) {
mutableSetValue(forKey: "tags").add(tag)
// Core Data 会自动添加到 tag.users
}
}
| 规则 | 行为 | 使用场景 |
|---|---|---|
| Nullify | 将关系设为 nil | 可选关系 |
| Cascade | 删除相关对象 | 拥有的子对象 (User → Posts) |
| Deny | 如果存在相关对象则阻止删除 | 保护被引用的数据 |
| No Action | 不执行任何操作(需要手动清理) | 很少适用 |
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)
)
}
}
// ❌ 错误: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) // 已加载
}
let request = User.fetchRequest()
request.fetchBatchSize = 20 // 根据需要每次加载 20 个
request.returnsObjectsAsFaults = true // 默认值,内存高效
以下情况自动处理:
添加可选属性
移除属性
重命名(使用重命名标识符)
添加具有可选值或默认值的关系
let description = NSPersistentStoreDescription() description.shouldMigrateStoreAutomatically = true description.shouldInferMappingModelAutomatically = true
更改属性类型
拆分/合并实体
复杂的关系变更
迁移期间的数据转换
// 在 Xcode 中创建映射模型: // File → New → Mapping Model // 选择源模型和目标模型
发布前必须执行:
// ❌ 错误:所有操作使用一个上下文
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 {
// 安全的后台工作
}
}
// ❌ 错误:每次渲染都获取数据
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) { ... } // 自动更新
}
// ❌ 错误:没有合并策略(冲突会导致崩溃)
let context = container.viewContext
// ✅ 正确:定义合并行为
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true
-com.apple.CoreData.SQLDebug 1情况:新的 iOS 17 功能可用,项目中期有迁移的诱惑。
风险:迁移很复杂。混合使用 Core Data + SwiftData 存在尖锐问题。
回应:"先完成当前的里程碑。迁移需要专门的时间和测试。"
情况:仅在模拟器中测试了架构变更。
风险:模拟器在重建时会删除数据库。真实设备保留持久化数据并会崩溃。
回应:"必须:在真实设备上用真实数据测试。现在花 15 分钟可以防止生产环境崩溃。"
在 tvOS 上使用 CoreData + CloudKit 是危险的。 CloudKit 元数据会导致本地存储空间显著膨胀,而 tvOS 没有持久化的本地存储 — 系统可能随时删除 Caches(包括 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 模式每周安装次数
93
代码仓库
GitHub 星标数
601
首次出现
2026年1月21日
安全审计
安装于
opencode79
codex73
claude-code73
gemini-cli72
cursor71
github-copilot69
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 :
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)
If ANY of these appear, STOP:
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()
}
}
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
}()
}
NEVER pass NSManagedObject across threads. Pass objectID instead.
// ❌ 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
// 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
}
}
| 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 |
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)
)
}
}
// ❌ 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
Handled automatically for:
Adding optional attributes
Removing attributes
Renaming (with renaming identifier)
Adding relationships with optional or default value
let description = NSPersistentStoreDescription() description.shouldMigrateStoreAutomatically = true description.shouldInferMappingModelAutomatically = true
Changing attribute types
Splitting/merging entities
Complex relationship changes
Data transformation during migration
// Create mapping model in Xcode: // File → New → Mapping Model // Select source and destination models
MANDATORY before shipping :
// ❌ 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
}
}
// ❌ 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
}
// ❌ WRONG: No merge policy (conflicts crash)
let context = container.viewContext
// ✅ CORRECT: Define merge behavior
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true
-com.apple.CoreData.SQLDebug 1Situation : 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."
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."
CoreData + CloudKit is dangerous on tvOS. CloudKit metadata causes significant space inflation in the local store, and tvOS has no persistent local storage — the system deletes Caches (including Application Support) at any time. The inflated store plus random deletion is a worst-case combination.
Recommendation : Use SQLiteData with CloudKit SyncEngine instead for tvOS data persistence. See axiom-tvos for full tvOS storage constraints.
axiom-core-data-diag — Debugging migrations, thread errors, N+1 queriesaxiom-swiftdata — Modern alternative for iOS 17+axiom-database-migration — Safe schema evolution patternsaxiom-swift-concurrency — Async/await patterns for Core DataWeekly Installs
93
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
opencode79
codex73
claude-code73
gemini-cli72
cursor71
github-copilot69
Kotlin Exposed ORM 模式指南:DSL查询、DAO、事务管理与生产配置
952 周安装
Hacker News API 使用指南:免费获取热门技术故事、评论和用户数据
2,500 周安装
Elastic Security 告警分诊技能:自动化安全告警调查、分类与案例管理
127 周安装
Smithery AI CLI:AI智能体市场命令行工具,连接10万+工具与技能
2,600 周安装
Linear CLI 命令行工具:从终端管理 Linear 问题,集成 Git 和 JJ
2,500 周安装
RivetKit Actor 运行时 - 构建高性能长生命周期内存进程,适用于AI智能体与实时协作
2,500 周安装
PostgreSQL数据库管理指南 - 从模式设计到运维监控完整教程
2,500 周安装