The final piece of the CRUD puzzle covers deleting records from a persistent store. I can assure you that deleting records is no rocket science.

We also take a closer look at the NSFetchRequest class. In the previous tutorial, we used this class to fetch the records of an entity. But NSFetchRequest has a lot more to offer.

If you want to follow along, download the source files at the bottom of this tutorial.

How to Delete a Record From a Persistent Store

Remember from the previous tutorial that an item record is added to the list record every time the application is run. Add several item records to the list record by running the application a few times. This is what you should see in the console:

number of lists: 1
--
number of items: 10
---
Item 9
Item 3
Item 10
Item 4
Item 5
Item 6
Item 7
Item 1
Item 8
Item 2

Deleting a record from a persistent store involves three steps:

  • fetch the record that needs to be deleted
  • mark the record for deletion
  • save the changes

To show you how to delete a record, we delete an item record from the list record every time the application is run. If the list record has no item records left, we delete the list record itself.

Since we are focusing on deleting items, you can remove or comment out the code we added to application(_:didFinishLaunchingWithOptions:) to add new item records.

Deleting an Item Record

Remember that we obtained the list's item records by invoking mutableSetValue(forKey:). It returns a mutable set (NSMutableSet) of NSManagedObject instances. We can obtain a random item record by invoking anyObject() on the mutable set.

let items = list.mutableSetValue(forKey: "items")

if let anyItem = items.anyObject() as? NSManagedObject {
    print(anyItem.value(forKey: "name") ?? "no name")
}

If you run the application, the name of a random item record is printed to the console. To delete the item record, we invoke delete(_:) on the managed object context the item record belongs to, passing in the item record as an argument. Remember that every managed object is tied to a managed object context.

let items = list.mutableSetValue(forKey: "items")

if let anyItem = items.anyObject() as? NSManagedObject {
    managedObjectContext.delete(anyItem)
}

Every time you run the application, a random item record is deleted from the persistent store.

number of lists: 1
--
number of items: 5
---
Item 8
Item 9
Item 7
Item 2
Item 10

Deleting a List Record

If no item records are left, we delete the list record to which the item records belonged.

let items = list.mutableSetValue(forKey: "items")

if let anyItem = items.anyObject() as? NSManagedObject {
    managedObjectContext.delete(anyItem)
} else {
    managedObjectContext.delete(list)
}
number of lists: 1
--
number of items: 0
---

More Fetching

Seeding the Persistent Store

Fetching records is a common task when working with Core Data. To show you how you can customize a fetch request, we first need to populate the database with some data. Open AppDelegate.swift and implement the seedPersistentStoreWithManagedObjectContext(_:) method as shown below.

private func seedPersistentStoreWithManagedObjectContext(_ managedObjectContext: NSManagedObjectContext) {
    guard !UserDefaults.standard.bool(forKey: didSeedPersistentStore) else { return }

    let listNames = ["Home", "Work", "Leisure"]

    for listName in listNames {
        // Create List
        if let list = createRecordForEntity("List", inManagedObjectContext: managedObjectContext) {
            // Populate List
            list.setValue(listName, forKey: "name")
            list.setValue(Date(), forKey: "createdAt")

            // Add Items
            for i in 1...10 {
                // Create Item
                if let item = createRecordForEntity("Item", inManagedObjectContext: managedObjectContext) {
                    // Set Attributes
                    item.setValue("Item \(i)", forKey: "name")
                    item.setValue(Date(), forKey: "createdAt")
                    item.setValue(NSNumber(value: (i % 3 == 0)), forKey: "completed")

                    // Set List Relationship
                    item.setValue(list, forKey: "list")
                }
            }
        }
    }

    do {
        // Save Managed Object Context
        try managedObjectContext.save()

    } catch {
        print("Unable to save managed object context.")
    }

    // Update User Defaults
    UserDefaults.standard.set(true, forKey: didSeedPersistentStore)
}

We first check whether the persistent store has already been seeded. We only want to seed it once. The key, didSeedPersistentStore, is a private constant property of the AppDelegate class.

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    let didSeedPersistentStore = "didSeedPersistentStore"

    let coreDataManager = CoreDataManager(modelName: "Lists")

    ...

}

We then loop through an array of strings, the names for the list records, and create a list record for each string. We set the name and createdAt attributes of the list record and add ten item records to each list record.

Note that we mark some of the item records as completed by setting their completed attribute to true. We also set the list relationship of each item record to the current list record. We push the changes to the persistent store by saving the managed object context and we set didSeedPersistentStore to true in the user defaults database.

Update application(_:didFinishLaunchingWithOptions:) as shown below to seed the persistent store when the application did finish launching.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let managedObjectContext = coreDataManager.managedObjectContext

    // Seed Persistent Store
    seedPersistentStoreWithManagedObjectContext(managedObjectContext)

    return true
}

In production, seeding the persistent store on launch is not something you want to do in application(_:didFinishLaunchingWithOptions:). If your application takes too long to launch, the operating system's watchdog kills your application. The watchdog has no mercy.

Sorting Records

The order of the records returned by a fetch request is undefined. The users of your application expect more from you, though. Sorting records is a common task. Add the following code snippet to application(_:didFinishLaunchingWithOptions:), after seeding the persistent store.

// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "List")

// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]

do {
    let records = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]

    for record in records {
        print(record.value(forKey: "name") ?? "no name")
    }

} catch {
    print(error)
}

We create a fetch request for the List entity. To sort the records returned by the fetch request, we add a sort descriptor, an instance of the NSSortDescriptor class. We initialize it by invoking its init(key:ascending:) initializer, passing in the property key to use for comparison and the sort order.

We then set the sortDescriptors property of the fetch request. The sortDescriptors property is of type [NSSortDescriptor], which means we can combine multiple sort descriptors. The order of the sort descriptors determines in which order the sort descriptors are applied.

We execute the fetch request and print the names of the records to the console.

Home
Leisure
Work

Predicates

We can use predicates to refine the result of a fetch request. Predicates, instances of the NSPredicate class, enable us to specify which records we are interested in. Update the previous example as shown below. The syntax of the predicate is reminiscent of SQL.

// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "List")

// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]

// Add Predicate
let predicate = NSPredicate(format: "name CONTAINS[c] %@", "o")
fetchRequest.predicate = predicate

do {
    let records = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]

    for record in records {
        print(record.value(forKey: "name") ?? "no name")
    }

} catch {
    print(error)
}

The predicate defines that we are only interested in list records with a name that includes or contains the letter o. The [c] specifier indicates that the case of the letter o should be ignored. This is what the result looks like in the console.

Home
Work

Predicates also work great with relationships. Replace the previous example with the following:

// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")

// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]

// Add Predicate
let predicate1 = NSPredicate(format: "completed = 1")
let predicate2 = NSPredicate(format: "%K = %@", "list.name", "Home")
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1, predicate2])

do {
    let records = try managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]

    for record in records {
        print(record.value(forKey: "name") ?? "no name")
    }

} catch {
    print(error)
}

This example fetches item records and sorts them by name. In the example, we instantiate two predicates and turn them into a compound predicate, using the NSCompoundPredicate class. The first predicate restricts the fetch request to item records that are marked as completed. The second predicate specifies that we are only interested in item records that belong to a list record that has a name equal to Home. Run the application and inspect the result in the console.

Item 3
Item 6
Item 9

To learn more about the syntax of predicates, I recommend reading Apple's Predicate Format String Syntax guide. The %K format specifier, for example, is a variable argument substitution for a key path.

Now that you know what Core Data is and how the Core Data stack is set up, it's time to write some code. If you're serious about Core Data, check out Core Data Fundamentals. In this series, we build an application that is powered by Core Data and you learn everything you need to know to use Core Data in your own projects.