Saving and fetching data
6. Saving and fetching data
Our new NSPersistentContainer
comes bundled with a viewContext
property. This property allows us to perform Core Data operations on the main thread. Whenever we initialize a new Core Data entity, we must point it to a managed context.
The managed context acts as a sort of manager which keeps track of our Core Data entities. It knows which changes are persisted and which are not. With our persistent container set up, let’s add the functionality for saving a new StoredTaskItem
entity.
func saveTaskItem(_ taskItem: TaskItem) {
// 1.
let entity = StoredTaskItem(context: persistentContainer.viewContext)
// 2.
entity.title = taskItem.title
entity.subtitle = taskItem.subtitle
entity.isCompleted = taskItem.isCompleted
// 3.
do {
try persistentContainer.viewContext.save()
} catch {
// 4.
persistentContainer.viewContext.rollback()
}
}
We created a new function which takes our simple TaskItem
struct and uses it to create a new StoredTaskItem
Core Data entity.
-
We initialize a
StoredTaskItem
by pointing it to theviewContext
created by theNSPersistentContainer
. This allows us to perform Core Data operations on the main thread; -
We populate the
title
,subtitle
andisCompleted
fields; -
Finally, we attempt to persist our changed by calling the
save()
method. In case something fails (e.g., a required field was not set), then the save process fails and an exception is being thrown. -
In case we do encounter an exception, we call
rollback()
to reset our pending changes.
A helpful way of looking at managed contexts is through the lens of Git. When we create a new StoredTaskItem
with a managed context, we’re basically making the equivalent operation as git add
- meaning that we’re adding files to the staging environment, but they’re not yet committed.
Only after we call viewContext.save()
we actually persist our changes, which in terms of Git, is the equivalent of git commit
.
Fetching Data
With our saving functionality in place, let’s look at how we can actually fetch data from Core Data. Take a look at the following method:
func fetchTaskItems() throws -> [TaskItem] {
// 1.
let fetchRequest = StoredTaskItem.fetchRequest()
// 2.
let fetchedItems = try persistentContainer.viewContext.fetch(fetchRequest)
// 3.
return fetchedItems.map {
TaskItem(
title: $0.title ?? "N/A",
subtitle: $0.subtitle,
isCompleted: $0.isCompleted
)
}
}
Fetching items takes place with the help of NSFetchRequest
. A fetch request basically specifies what, and how items should be fetched from the persistent storage. Let’s break this down step by step:
- When we enable
Codegen
in our Core Data model editor, it will not only create our entity classes for us, but also generate a helpful fetch request method for us. It is mostly for convenience, and there is no difference from writing this manually ourselves as such:
let request = NSFetchRequest<StoredTaskItem>()
-
With our fetch request ready, we simply ask our managed context to fetch items from Core Data that satisfy our fetch request. In our case, fetch all
StoredTaskItem
items. -
Finally, since we don’t want to work with
StoredTaskItem
directly, but instead work withTaskItem
instead, we map the response before returning it. Note how all properties ofStoredTaskItem
are all optional by default. This is howCodegen
generates our class. If we prefer to have non-optional fields, we would need to disable code generation and manually create the entity class ourselves.
Filtering and sorting data:
As mentioned previously, fetch requests contain all the necessary information regarding what and how something should be fetched from Core Data. Let’s explore how we can customize our fetch request even further.
Here’s a new method, which fetches only completed items and returns them in alphabetical order:
func fetchCompletedItems() throws -> [TaskItem] {
// 1.
let fetchRequest = StoredTaskItem.fetchRequest()
// 2.
fetchRequest.predicate = NSPredicate(
format: "isCompleted == %@",
NSNumber(value: true)
)
// 3.
fetchRequest.sortDescriptors = [
NSSortDescriptor(keyPath: \StoredTaskItem.title, ascending: true)
]
// 4.
let fetchedItems = try persistentContainer.viewContext.fetch(fetchRequest)
return fetchedItems.map {
TaskItem(
title: $0.title ?? "N/A",
subtitle: $0.subtitle,
isCompleted: $0.isCompleted
)
}
}
-
As always, we start by creating our fetch request;
-
We then use an
NSPredicate
and specify a filter condition. In our case, return all items whereisCompleted == true
; -
We then specify that the items should be sorted based on their title, in ascending order;
-
Finally, we perform the fetch request and map the results;