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.