| name | swift-core-data |
| description | Persist data with Core Data - models, contexts, fetch requests, migrations, SwiftData |
| version | 2.0.0 |
| sasmp_version | 1.3.0 |
| bonded_agent | 04-swift-data |
| bond_type | PRIMARY_BOND |
Swift Core Data Skill
Data persistence framework knowledge for Core Data and SwiftData in Apple platforms.
Prerequisites
- Xcode 15+ installed
- Understanding of object graphs
- Basic SQL concepts helpful
Parameters
parameters:
framework:
type: string
enum: [core_data, swift_data]
default: swift_data
description: Persistence framework
cloudkit_sync:
type: boolean
default: false
lightweight_migration:
type: boolean
default: true
store_type:
type: string
enum: [sqlite, in_memory, binary]
default: sqlite
Topics Covered
Core Data vs SwiftData
| Feature | Core Data | SwiftData |
|---|
| Min iOS | 3.0+ | 17.0+ |
| Definition | .xcdatamodeld | @Model macro |
| Threading | Manual (contexts) | Actor-based |
| Fetch | NSFetchRequest | #Predicate |
| Learning Curve | Steep | Gentle |
Core Data Stack
| Component | Purpose |
|---|
| NSPersistentContainer | Encapsulates stack |
| NSManagedObjectContext | Working area for objects |
| NSManagedObjectModel | Schema definition |
| NSPersistentStoreCoordinator | Store management |
SwiftData Components
| Component | Purpose |
|---|
| ModelContainer | Schema + store |
| ModelContext | Working area |
| @Model | Entity macro |
| @Query | Fetch in SwiftUI |
Code Examples
SwiftData (iOS 17+)
import SwiftData
@Model
final class Task {
var title: String
var notes: String
var dueDate: Date?
var isCompleted: Bool
var priority: Priority
var createdAt: Date
@Relationship(deleteRule: .cascade, inverse: \Subtask.parentTask)
var subtasks: [Subtask] = []
@Relationship(inverse: \Tag.tasks)
var tags: [Tag] = []
init(title: String, notes: String = "", dueDate: Date? = nil, priority: Priority = .medium) {
self.title = title
self.notes = notes
self.dueDate = dueDate
self.isCompleted = false
self.priority = priority
.createdAt ()
}
}
{
title:
isCompleted:
parentTask: ?
(: , : ? ) {
.title title
.isCompleted
.parentTask parentTask
}
}
{
(.unique) name:
color:
tasks: [] []
(: , : ) {
.name name
.color color
}
}
: , , {
low, medium, high, urgent
}
: {
container:
() {
schema ([., ., .])
config (
schema: schema,
isStoredInMemoryOnly: ,
cloudKitDatabase: .none
)
{
container (for: schema, configurations: config)
} {
()
}
}
body: {
{
()
}
.modelContainer(container)
}
}
: {
(\.modelContext) context
(sort: \.dueDate) tasks: []
body: {
{
(tasks) { task
(task: task)
}
.onDelete(perform: deleteTasks)
}
}
( : ) {
index offsets {
context.delete(tasks[index])
}
}
}
: {
(filter: #<> { .isCompleted },
sort: [(\.priority, order: .reverse)])
tasks: []
body: {
(tasks) { task
(task.title)
}
}
}
Core Data (Traditional)
import CoreData
final class CoreDataStack {
static let shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Core Data error: \(error), \(error.userInfo)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
var viewContext: NSManagedObjectContext {
persistentContainer.viewContext
}
func newBackgroundContext() -> NSManagedObjectContext {
let context = persistentContainer.newBackgroundContext()
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
func saveContext() {
let context viewContext
context.hasChanges {
{
context.save()
} {
nsError error
()
}
}
}
}
{
<: >( : ., : ? , : [] ) -> [] {
request .fetchRequest()
request.predicate predicate
request.sortDescriptors sortDescriptors
fetch(request) [] []
}
}
{
<>( : () -> ) -> {
withCheckedThrowingContinuation { continuation
persistentContainer.performBackgroundTask { context
{
result block(context)
context.hasChanges {
context.save()
}
continuation.resume(returning: result)
} {
continuation.resume(throwing: error)
}
}
}
}
<: >(: [], : ) {
context newBackgroundContext()
context.perform {
insertRequest (entityName: entityName, objects: entities.map { entity -> [String: ]
data ().encode(entity)
.jsonObject(with: data) [String: ]
})
insertRequest.resultType .count
result context.execute(insertRequest)
()
.mergeChanges(
fromRemoteContextSave: [NSInsertedObjectsKey: []],
into: [.viewContext]
)
}
}
}
Migration Handling
let container = NSPersistentContainer(name: "DataModel")
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
description?.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
final class MigrationManager {
func requiresMigration(at storeURL: URL, for model: NSManagedObjectModel) -> Bool {
guard let metadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL) else {
return false
}
return !model.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata)
}
func migrateStore(at storeURL: URL, to destinationModel: ) {
}
}
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|
| "The model configuration is invalid" | Schema mismatch | Delete app, check @Model |
| Context save crash | Wrong thread | Use perform/performAndWait |
| Relationship fault | Object deleted | Check for nil before access |
| Slow fetch | Missing index | Add index to frequently queried attributes |
| Migration fails | Non-trivial change | Write custom mapping model |
Debug Tips
-com.apple.CoreData.SQLDebug 1
-com.apple.CoreData.Logging.stderr 1
-com.apple.SwiftData.SQLDebug 1
let request: NSFetchRequest<Task> = Task.fetchRequest()
print(request.description)
print("Inserted: \(context.insertedObjects.count)")
print("Updated: \(context.updatedObjects.count)")
print("Deleted: \(context.deletedObjects.count)")
Validation Rules
validation:
- rule: background_for_heavy_operations
severity: error
check: Use background context for batch operations
- rule: main_thread_for_ui
severity: error
check: Only access viewContext on main thread
- rule: index_frequently_queried
severity: warning
check: Add indexes to attributes used in predicates
Usage
Skill("swift-core-data")
Related Skills
swift-networking - Syncing remote data
swift-swiftui - @Query integration
swift-testing - In-memory stores for tests