iOS Core Data Architect
Expert in Core Data persistence, CloudKit synchronization, schema migrations, and migration paths to SwiftData.
Activation Triggers
Activate on: "Core Data", "NSManagedObject", "CloudKit sync", "Core Data migration", "NSFetchedResultsController", "NSPersistentContainer", "SwiftData migration", "lightweight migration", "Core Data performance"
NOT for: SwiftUI state management → swiftui-data-flow-expert | Server databases → data-pipeline-engineer | SQLite direct → mobile-offline-sync-architect
Quick Start
- Assess project state — new project? Use SwiftData. Existing Core Data? Evaluate migration vs coexistence.
- Design data model — entities, relationships, fetch indexes in the .xcdatamodeld editor
- Configure persistent container — NSPersistentCloudKitContainer for CloudKit sync, NSPersistentContainer for local-only
- Implement migration plan — lightweight (automatic) or heavyweight (mapping model) depending on schema changes
- Optimize fetching — batch size, prefetching, background contexts for heavy operations
Core Capabilities
| Domain |
Technologies |
| Persistence |
NSPersistentContainer, NSManagedObjectContext, WAL mode |
| CloudKit |
NSPersistentCloudKitContainer, CKRecord zone, conflict resolution |
| Migrations |
Lightweight migration, mapping models, progressive migration |
| Performance |
NSBatchInsertRequest, NSBatchDeleteRequest, faulting, prefetch |
| SwiftData |
Coexistence with Core Data, migration path, @Model from NSManagedObject |
Architecture Patterns
Core Data Stack Setup with CloudKit
class PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "MyApp")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No store description")
}
if inMemory {
description.url = URL(fileURLWithPath: "/dev/null")
}
// CloudKit configuration
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.example.myapp"
)
// Enable remote change notifications
description.setOption(true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// Enable persistent history tracking
description.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
container.loadPersistentStores { _, error in
if let error { fatalError("Store failed: \(error)") }
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
// Background context for heavy operations
func newBackgroundContext() -> NSManagedObjectContext {
let context = container.newBackgroundContext()
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
}
Progressive Migration Strategy
Version 1 → Version 2 (add optional column):
└─ Lightweight migration (automatic)
Version 2 → Version 3 (rename attribute):
└─ Mapping model required (heavyweight)
Version 3 → Version 4 (split entity):
└─ Custom migration with NSEntityMigrationPolicy
Strategy: Progressive migration chain
v1 → v2 → v3 → v4 (each step is a known migration)
NOT: v1 → v4 directly (complex, error-prone)
Code:
for migration in migrationChain {
try coordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL,
options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: migration.isLightweight]
)
}
SwiftData Coexistence (Migration Path)
// Phase 1: Core Data and SwiftData side-by-side
// Share the same SQLite store file
let schema = Schema([NewEntity.self]) // SwiftData models
let config = ModelConfiguration(
url: existingCoreDataStoreURL // Same store as Core Data
)
let container = try ModelContainer(for: schema, configurations: [config])
// Phase 2: Gradually move entities from Core Data to SwiftData
// - New entities: @Model (SwiftData)
// - Existing entities: NSManagedObject (Core Data)
// - Read from both, write to SwiftData for new data
// Phase 3: Full migration
// - Convert all NSManagedObject subclasses to @Model
// - Remove .xcdatamodeld file
// - Use ModelContainer exclusively
Anti-Patterns
- Using viewContext for writes — viewContext is on the main thread; heavy writes block the UI. Use
performBackgroundTask or newBackgroundContext() for inserts and batch operations.
- No batch operations for bulk data — inserting 10K records one-by-one creates 10K change notifications. Use
NSBatchInsertRequest which bypasses the context and writes directly.
- Ignoring faulting — accessing all properties of all objects in a list. Core Data uses faulting to lazy-load; respect it by only accessing displayed properties. Set
fetchBatchSize.
- Heavyweight migration without testing — mapping models are complex and fail silently with data corruption. Test every migration path against real production data copies.
- Skipping persistent history tracking — required for CloudKit sync and multi-process coordination (widgets, extensions). Always enable
NSPersistentHistoryTrackingKey.
Quality Checklist
[ ] Persistent container configured correctly (CloudKit or local)
[ ] viewContext used only for reads; background context for writes
[ ] NSBatchInsertRequest used for bulk operations
[ ] Fetch requests have fetchBatchSize set (typically 20-50)
[ ] Migration plan documented for each model version
[ ] Lightweight migration tested between all adjacent versions
[ ] CloudKit sync tested (if applicable) with conflict resolution
[ ] Persistent history tracking enabled
[ ] NSFetchedResultsController used for table/list data sources
[ ] Background context mergePolicy set explicitly
[ ] Unit tests use in-memory store for speed
[ ] SwiftData migration path documented for future transition
1---2name: ios-core-data-architect3description: iOS Core Data architect for persistent storage, CloudKit sync, schema migrations, and SwiftData migration. Activate on: Core Data, NSManagedObject, CloudKit sync, Core Data migration, NSFetchedResultsController, NSPersistentContainer, SwiftData migration path. NOT for: SwiftUI state management (use swiftui-data-flow-expert), server databases (use data-pipeline-engineer), SQLite direct (use mobile-offline-sync-architect).4license: Apache-2.05---6
7# iOS Core Data Architect
8
9Expert in Core Data persistence, CloudKit synchronization, schema migrations, and migration paths to SwiftData.
10
11## Activation Triggers
12
13**Activate on:** "Core Data", "NSManagedObject", "CloudKit sync", "Core Data migration", "NSFetchedResultsController", "NSPersistentContainer", "SwiftData migration", "lightweight migration", "Core Data performance"
14
15**NOT for:** SwiftUI state management → `swiftui-data-flow-expert` | Server databases → `data-pipeline-engineer` | SQLite direct → `mobile-offline-sync-architect`
16
17## Quick Start
18
191. **Assess project state** — new project? Use SwiftData. Existing Core Data? Evaluate migration vs coexistence.
202. **Design data model** — entities, relationships, fetch indexes in the .xcdatamodeld editor
213. **Configure persistent container** — NSPersistentCloudKitContainer for CloudKit sync, NSPersistentContainer for local-only
224. **Implement migration plan** — lightweight (automatic) or heavyweight (mapping model) depending on schema changes
235. **Optimize fetching** — batch size, prefetching, background contexts for heavy operations
24
25## Core Capabilities
26
27| Domain | Technologies |
28|--------|-------------|
29| **Persistence** | NSPersistentContainer, NSManagedObjectContext, WAL mode |
30| **CloudKit** | NSPersistentCloudKitContainer, CKRecord zone, conflict resolution |
31| **Migrations** | Lightweight migration, mapping models, progressive migration |
32| **Performance** | NSBatchInsertRequest, NSBatchDeleteRequest, faulting, prefetch |
33| **SwiftData** | Coexistence with Core Data, migration path, @Model from NSManagedObject |
34
35## Architecture Patterns
36
37### Core Data Stack Setup with CloudKit
38
39```swift
40class PersistenceController {
41 static let shared = PersistenceController()
42
43 let container: NSPersistentCloudKitContainer
44
45 init(inMemory: Bool = false) {
46 container = NSPersistentCloudKitContainer(name: "MyApp")
47
48 guard let description = container.persistentStoreDescriptions.first else {
49 fatalError("No store description")
50 }
51
52 if inMemory {
53 description.url = URL(fileURLWithPath: "/dev/null")
54 }
55
56 // CloudKit configuration
57 description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
58 containerIdentifier: "iCloud.com.example.myapp"
59 )
60
61 // Enable remote change notifications
62 description.setOption(true as NSNumber,
63 forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
64
65 // Enable persistent history tracking
66 description.setOption(true as NSNumber,
67 forKey: NSPersistentHistoryTrackingKey)
68
69 container.loadPersistentStores { _, error in
70 if let error { fatalError("Store failed: \(error)") }
71 }
72
73 container.viewContext.automaticallyMergesChangesFromParent = true
74 container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
75 }
76
77 // Background context for heavy operations
78 func newBackgroundContext() -> NSManagedObjectContext {
79 let context = container.newBackgroundContext()
80 context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
81 return context
82 }
83}
84```
85
86### Progressive Migration Strategy
87
88```
89Version 1 → Version 2 (add optional column):
90 └─ Lightweight migration (automatic)
91
92Version 2 → Version 3 (rename attribute):
93 └─ Mapping model required (heavyweight)
94
95Version 3 → Version 4 (split entity):
96 └─ Custom migration with NSEntityMigrationPolicy
97
98Strategy: Progressive migration chain
99 v1 → v2 → v3 → v4 (each step is a known migration)
100 NOT: v1 → v4 directly (complex, error-prone)
101
102Code:
103 for migration in migrationChain {
104 try coordinator.addPersistentStore(
105 ofType: NSSQLiteStoreType,
106 configurationName: nil,
107 at: storeURL,
108 options: [NSMigratePersistentStoresAutomaticallyOption: true,
109 NSInferMappingModelAutomaticallyOption: migration.isLightweight]
110 )
111 }
112```
113
114### SwiftData Coexistence (Migration Path)
115
116```swift
117// Phase 1: Core Data and SwiftData side-by-side
118// Share the same SQLite store file
119let schema = Schema([NewEntity.self]) // SwiftData models
120let config = ModelConfiguration(
121 url: existingCoreDataStoreURL // Same store as Core Data
122)
123let container = try ModelContainer(for: schema, configurations: [config])
124
125// Phase 2: Gradually move entities from Core Data to SwiftData
126// - New entities: @Model (SwiftData)
127// - Existing entities: NSManagedObject (Core Data)
128// - Read from both, write to SwiftData for new data
129
130// Phase 3: Full migration
131// - Convert all NSManagedObject subclasses to @Model
132// - Remove .xcdatamodeld file
133// - Use ModelContainer exclusively
134```
135
136## Anti-Patterns
137
1381. **Using viewContext for writes** — viewContext is on the main thread; heavy writes block the UI. Use `performBackgroundTask` or `newBackgroundContext()` for inserts and batch operations.
1392. **No batch operations for bulk data** — inserting 10K records one-by-one creates 10K change notifications. Use `NSBatchInsertRequest` which bypasses the context and writes directly.
1403. **Ignoring faulting** — accessing all properties of all objects in a list. Core Data uses faulting to lazy-load; respect it by only accessing displayed properties. Set `fetchBatchSize`.
1414. **Heavyweight migration without testing** — mapping models are complex and fail silently with data corruption. Test every migration path against real production data copies.
1425. **Skipping persistent history tracking** — required for CloudKit sync and multi-process coordination (widgets, extensions). Always enable `NSPersistentHistoryTrackingKey`.
143
144## Quality Checklist
145
146```
147[ ] Persistent container configured correctly (CloudKit or local)
148[ ] viewContext used only for reads; background context for writes
149[ ] NSBatchInsertRequest used for bulk operations
150[ ] Fetch requests have fetchBatchSize set (typically 20-50)
151[ ] Migration plan documented for each model version
152[ ] Lightweight migration tested between all adjacent versions
153[ ] CloudKit sync tested (if applicable) with conflict resolution
154[ ] Persistent history tracking enabled
155[ ] NSFetchedResultsController used for table/list data sources
156[ ] Background context mergePolicy set explicitly
157[ ] Unit tests use in-memory store for speed
158[ ] SwiftData migration path documented for future transition
159```