Saving and fetching data

Module 1 of 11 0%

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.

  1. We initialize a StoredTaskItem by pointing it to the viewContext created by the NSPersistentContainer. This allows us to perform Core Data operations on the main thread;

  2. We populate the title, subtitle and isCompleted fields;

  3. 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.

  4. 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:

  1. 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>()
  1. 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.

  2. Finally, since we don’t want to work with StoredTaskItem directly, but instead work with TaskItem instead, we map the response before returning it. Note how all properties of StoredTaskItem are all optional by default. This is how Codegen 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
        )
    }
}
  1. As always, we start by creating our fetch request;

  2. We then use an NSPredicate and specify a filter condition. In our case, return all items where isCompleted == true;

  3. We then specify that the items should be sorted based on their title, in ascending order;

  4. Finally, we perform the fetch request and map the results;