Persistence with Core Data
Reviewed & published by Brayan K
Core Data is Apple's framework for saving your app's objects on the device. You'll meet the NSManagedObjectContext, define entities, run fetch requests, drive a live list with @FetchRequest, and save changes.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • What the NSManagedObjectContext is and why it matters
- • How entities describe the shape of stored objects
- • Insert objects and persist them with save()
- • Query data with NSFetchRequest and fetch
- • Filter and sort with NSPredicate and sort descriptors
- • Build a live SwiftUI list with @FetchRequest
📊 Core Data Vocabulary
| Term | Meaning |
|---|---|
| Entity | Blueprint for a kind of object |
| NSManagedObject | An instance of an entity |
| Context | Scratchpad of unsaved changes |
| NSFetchRequest | A query for objects |
| save() | Write changes to disk |
1️⃣ Insert and Save
You create objects inside a context, set their attributes, then call save() to persist them. Nothing reaches disk until you save.
import CoreData
// The container builds the Core Data stack and gives you a context.
let container = NSPersistentContainer(name: "Notes")
container.loadPersistentStores { _, error in
if let error = error { fatalError("\(error)") }
}
let context = container.viewContext
// Create a new managed object for the "Note" entity and set attributes.
let note = NSManagedObject(entity: container.managedObjectModel.entitiesByName["Note"]!,
insertInto: context)
note.setValue("Buy milk", forKey: "title")
// save() writes pending changes to the persistent store on disk.
do {
try context.save()
print("Saved!")
} catch {
print("Save failed: \(error)")
}2️⃣ Fetch Requests
An NSFetchRequest describes what to load. Attach an NSPredicate to filter and sort descriptors to order, then call context.fetch(request).
import CoreData
// A fetch request describes WHAT to load. NSFetchRequest names the entity.
let request = NSFetchRequest<NSManagedObject>(entityName: "Note")
// Optional: filter with a predicate and sort the results.
request.predicate = NSPredicate(format: "title CONTAINS %@", "milk")
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
do {
let notes = try context.fetch(request) // runs the query
for note in notes {
print(note.value(forKey: "title") as? String ?? "untitled")
}
} catch {
print("Fetch failed: \(error)")
}Your turn. Fill in the request type and the fetch method.
// 🎯 YOUR TURN — replace each ___ then run in Xcode with a Core Data model.
import CoreData
// 1) Make a fetch request for the "Task" entity.
let request = ___<NSManagedObject>(entityName: "Task") // 👉 the request type
// 2) Execute it against the context.
let results = try context.___(request) // 👉 the method that fetches
for task in results {
print(task.value(forKey: "name") as? String ?? "")
}
// ✅ Prints the name of every saved Task.3️⃣ @FetchRequest in SwiftUI
In SwiftUI, @FetchRequest loads managed objects and keeps the view in sync — add or delete an object and the list updates automatically.
import SwiftUI
import CoreData
// In SwiftUI, @FetchRequest loads managed objects and keeps the view in sync.
struct NoteList: View {
@Environment(\.managedObjectContext) private var context
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "title", ascending: true)]
) private var notes: FetchedResults<Note>
var body: some View {
List(notes, id: \.self) { note in
Text(note.title ?? "untitled")
}
}
}Now you try. Fill in the delete and save methods.
// 🎯 YOUR TURN — delete then save.
import CoreData
let request = NSFetchRequest<NSManagedObject>(entityName: "Note")
let notes = try context.fetch(request)
if let first = notes.first {
context.___(first) // 👉 the method that removes a managed object
try context.___() // 👉 the method that persists the change
print("Deleted one note")
}
// ✅ Expected output:
// Deleted one notePro Tips
- 💡 Save deliberately. Batch related edits, then call save() once.
- 💡 Generate NSManagedObject subclasses so you use typed properties instead of setValue(forKey:).
- 💡 Use predicates to filter in the store, not in Swift — it's far faster on large data.
- 💡 Let @FetchRequest drive the UI so the list refreshes itself.
Common Errors (and the fix)
- Changes vanish after relaunch — you forgot try context.save().
- "entity not found" — the entity name string doesn't match your .xcdatamodeld exactly.
- Crash on background access — only use viewContext on the main thread.
- List doesn't refresh — you fetched manually; prefer @FetchRequest so SwiftUI observes changes.
📋 Quick Reference
| Task | Code | Result |
|---|---|---|
| Get context | container.viewContext | a workspace |
| Save | try context.save() | writes to disk |
| Build query | NSFetchRequest(entityName:) | a request |
| Run query | try context.fetch(request) | objects |
| Delete | context.delete(object) | marks removal |
| SwiftUI | @FetchRequest(...) | live results |
Mini-Challenge: Tiny Task Store
Create a Task, save it, then fetch the unfinished ones with a predicate and print their names.
// 🎯 MINI-CHALLENGE: A tiny task store
// Using a Core Data model with a "Task" entity (name: String, done: Bool):
// 1. Create a new Task, set its name to "Write tests" and done to false.
// 2. Save the context.
// 3. Fetch all Tasks where done == false using an NSPredicate.
// 4. Print each task's name.
//
// ✅ Example output:
// Write tests
// your code here🎉 Lesson Complete!
- ✅ The NSManagedObjectContext tracks changes before saving
- ✅ Entities describe the shape of stored objects
- ✅ save() writes pending changes to disk
- ✅ Query with NSFetchRequest + fetch, filtered by NSPredicate
- ✅ @FetchRequest drives a live SwiftUI list
Practice quiz
What is Core Data primarily used for?
- Drawing UI
- Persisting and managing an object graph on the device
- Making network calls
- Animating views
Answer: Persisting and managing an object graph on the device. Core Data manages and persists a graph of model objects locally.
What does an NSManagedObjectContext represent?
- A scratchpad for creating, editing, and saving managed objects
- A network session
- A SwiftUI view
- A JSON decoder
Answer: A scratchpad for creating, editing, and saving managed objects. The context is the in-memory workspace where you make and track changes before saving.
In Core Data, an 'entity' is most like...
- A function
- A thread
- A table or class describing a kind of object
- A URL
Answer: A table or class describing a kind of object. An entity defines the structure (attributes) of a type of stored object.
Which type do you use to query for objects?
- URLRequest
- NSFetchRequest
- JSONDecoder
- DispatchQueue
Answer: NSFetchRequest. NSFetchRequest describes what objects to fetch and how to filter/sort them.
Which method actually fetches results from a context?
- context.save()
- context.delete()
- context.reset()
- context.fetch(request)
Answer: context.fetch(request). context.fetch(request) executes a fetch request and returns the matching objects.
How do you persist changes to disk?
- context.commit()
- context.save()
- context.flush()
- context.write()
Answer: context.save(). Calling save() on the context writes pending changes to the persistent store.
Which property wrapper loads and observes Core Data results in SwiftUI?
- @FetchRequest
- @State
- @Published
- @Binding
Answer: @FetchRequest. @FetchRequest fetches managed objects and keeps the view updated as data changes.
To refine which objects a fetch returns, you set its...
- timeout
- statusCode
- predicate (NSPredicate)
- scheme
Answer: predicate (NSPredicate). An NSPredicate on the fetch request filters which objects match.
How do you remove a managed object?
- context.delete(object)
- object.remove()
- context.drop()
- object = nil
Answer: context.delete(object). context.delete(object) marks it for deletion; save() makes it permanent.
An NSPersistentContainer mainly provides...
- A view hierarchy
- A network layer
- A JSON encoder
- The Core Data stack including the context and store
Answer: The Core Data stack including the context and store. NSPersistentContainer sets up and holds the Core Data stack for you.
Continue this course
- Previous: Networking with URLSession & async/await
- Next: SwiftData Basics — @Model, ModelContainer and @Query — the modern persistence API
Frequently asked questions
What is an NSManagedObjectContext?
The context is Core Data's in-memory workspace, sometimes called a scratchpad. You create, edit, and delete managed objects within it, and it tracks every change. Nothing touches disk until you call save() on the context, at which point the pending changes are written to the persistent store. A typical app uses the container's viewContext on the main thread.
What is the difference between an entity and a managed object?
An entity is the blueprint: it defines a type of stored object and its attributes, configured in the .xcdatamodeld file. A managed object (an NSManagedObject or a generated subclass) is an actual instance of that entity living in a context. Think of the entity as the class and the managed object as an object of that class.
How do I query for stored objects?
Build an NSFetchRequest naming the entity, optionally attach an NSPredicate to filter and NSSortDescriptors to order results, then call context.fetch(request). It returns an array of matching managed objects. fetch is a throwing call, so use try inside a do/catch. The predicate format string uses Core Data's query syntax, such as 'done == NO'.
What does @FetchRequest do in SwiftUI?
@FetchRequest is a property wrapper that runs a fetch and exposes the results as a FetchedResults collection your view can iterate. Crucially it observes the context, so when objects are inserted, updated, or deleted, the view automatically refreshes. You provide sort descriptors and an optional predicate, and read the managed object context from the environment.
How do I delete an object?
Call context.delete(object) to mark a managed object for removal, then call context.save() to make the deletion permanent in the persistent store. Until you save, the deletion only exists in the context. In SwiftUI you often delete inside an onDelete handler and then save, which lets @FetchRequest update the list automatically.