Even though Core Data has been around for many years on OS X and iOS, a feature that was added only recently are batch updates. Developers have been asking for this feature for many years and Apple finally found a way to integrate it into Core Data. In this tutorial, I will show you how batch updates work and what they mean for the Core Data framework.
1. The Problem
Core Data is great at managing object graphs. Even complex object graphs with many entities and relationships aren't much of a problem for Core Data. However, Core Data does have a few weak spots, updating large numbers of records being one of them.
The problem is easy to understand. Whenever you update a record, Core Data loads the record into memory, updates the record, and saves the changes to the persistent store, a SQLite database for example.
If Core Data needs to update a large number of records, it needs to load every record into memory, update the record, and send the changes to the persistent store. If the number of records is too large, iOS will simply bail out due to a lack of resources. Even though a device running OS X may have the resources to execute the request, it will be slow and consume a lot of memory.
An alternative approach is to update the records in batches, but that too takes a lot of time and resources. On iOS 7, it's the only option iOS developers have. That's no longer the case since iOS 8 and OS X Yosemite.
2. The Solution
On iOS 8 and up and OS X Yosemite and up, it's possible to talk directly to the persistent store and tell it what you'd like to change. This generally involves updating an attribute. Apple calls this feature batch updates.
There are a number of pitfalls to watch out for though. Core Data does a lot of things for you and you may not even realize it until you use batch updates. Validation is a good example. Because Core Data performs batch updates directly on the persistent store, such as a SQLite database, Core Data isn't able to perform any validation on the data you write to the persistent store. This means that you are in charge of making sure you don't add invalid data to the persistent store.
When would you use batch updates? Apple recommends to only use this feature if the traditional approach is too resource or time intensive. If you need to mark hundreds or thousands of email messages as read, then batch updates is the best solution on iOS 8 and up and OS X Yosemite and up.
3. How Does It Work?
To illustrate how batch updates work, I suggest we revisit Done, a simple Core Data application that manages a to-do list. We'll add a button to the navigation bar that marks every item in the list as done.
Step 1: Projet Setup
Download or clone the project from GitHub and open it in Xcode 7. Run the application in the simulator and add a few to-do items.

Step 2: Create Bar Button Item
Open ViewController.swift and declare a property, checkAllButton, of type UIBarButtonItem at the top.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
    let ReuseIdentifierToDoCell = "ToDoCell"
    
    @IBOutlet weak var tableView: UITableView!
    
    var managedObjectContext: NSManagedObjectContext!
    
    var checkAllButton: UIBarButtonItem!
    
    ...
}
Initialize the bar button item in the viewDidLoad() method of the ViewController class and set it as the left bar button item of the navigation item.
// Initialize Check All Button checkAllButton = UIBarButtonItem(title: "Check All", style: .Plain, target: self, action: "checkAll:") // Configure Navigation Item navigationItem.leftBarButtonItem = checkAllButton
Step 3: Implement checkAll(_:) Method
The checkAll(_:) method is fairly easy, but there are a few caveats to watch out for. Take a look at its implementation below.
func checkAll(sender: UIBarButtonItem) {
    // Create Entity Description
    let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
    
    // Initialize Batch Update Request
    let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
    
    // Configure Batch Update Request
    batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
    batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
    
    do {
        // Execute Batch Request
        let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
        
        // Extract Object IDs
        let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
        
        for objectID in objectIDs {
            // Turn Managed Objects into Faults
            let managedObject = managedObjectContext.objectWithID(objectID)
            managedObjectContext.refreshObject(managedObject, mergeChanges: false)
        }
        
        // Perform Fetch
        try self.fetchedResultsController.performFetch()
        
    } catch {
        let updateError = error as NSError
        print("\(updateError), \(updateError.userInfo)")
    }
}
Create Batch Request
We start by creating an NSEntityDescription instance for the Item entity and use it to initialize an NSBatchUpdateRequest object. The NSBatchUpdateRequest class is a subclass of NSPersistentStoreRequest.
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
We set the result type of the batch update request to .UpdatedObjectIDsResultType, a member value of the NSBatchUpdateRequestResultType enum. This means that the result of the batch update request will be an array containing the object IDs, instances of the NSManagedObjectID class, of the records that were changed by the batch update request.
// Configure Batch Update Request batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
We also populate the propertiesToUpdate property of the batch update request. For this example, we set propertiesToUpdate to a dictionary containing one key, "done", with value NSNumber(bool: true). This means that every Item record will be set to done, which is what we're after.
// Configure Batch Update Request batchUpdateRequest.propertiesToUpdate = ["done": NSNumber(bool: true)]
Execute Batch Update Request
Even though batch updates bypass the managed object context, executing a batch update request is done by calling executeRequest(_:) on an NSManagedObjectContext instance. This method accepts one argument, an instance of the NSPersistentStoreRequest class. Because executeRequest(_:) is a throwing method, we execute the method in a do-catch statement.
do {
    // Execute Batch Request
    let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
    
} catch {
    let updateError = error as NSError
    print("\(updateError), \(updateError.userInfo)")
}
Updating the Managed Object Context
Even though we hand the batch update request to a managed object context, the managed object context is not aware of the changes as a result of executing the batch update request. As I mentioned earlier, it bypasses the managed object context. This gives batch updates their power and speed. To remedy this issue, we need to do two things:
- turn the managed objects that were updated by the batch update into faults
- tell the fetched results controller to perform a fetch to update the user interface
This is what we do in the next few lines of the checkAll(_:) method in the do clause of the do-catch statement. If the batch update request is successful, we extract the array of NSManagedObjectID instances from the NSBatchUpdateResult object.
// Extract Object IDs let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
We then iterate over the objectIDs array, ask the managed object context for the corresponding NSManagedObject instance, and turn it into a fault by invoking refreshObject(_:mergeChanges:), passing in the managed object as the first argument. To force the managed object into a fault, we pass false as the second argument.
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
    // Turn Managed Objects into Faults
    let managedObject = managedObjectContext.objectWithID(objectID)
    managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
Fetching Updated Records
The last step is to tell the fetched results controller to perform a fetch to update the user interface. If this is unsuccessful, we catch the error in the catch clause of the do-catch statement.
do {
    // Execute Batch Request
    let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
    
    // Extract Object IDs
    let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
    
    for objectID in objectIDs {
        // Turn Managed Objects into Faults
        let managedObject = managedObjectContext.objectWithID(objectID)
        managedObjectContext.refreshObject(managedObject, mergeChanges: false)
    }
    
    // Perform Fetch
    try self.fetchedResultsController.performFetch()
    
} catch {
    let updateError = error as NSError
    print("\(updateError), \(updateError.userInfo)")
}
While this may seem cumbersome and fairly complex for an easy operation, keep in mind that we bypass the Core Data stack. In other words, we need to take care of some housekeeping that's usually done for us by Core Data.
Step 4: Build & Run
Build the project and run the application in the simulator or on a physical device. Click or tap the bar button item on the right to check every to-do item in the list.

Conclusion
Batch updates are a great addition to the Core Data framework. Not only does it answer a need developers have had for many years, it isn't difficult to implement as long as you keep a few basic rules in mind. In the next tutorial, we'll take a closer look at batch deletes, another feature of the Core Data framework that was added only recently.
 
                 
                                    
Comments