Upcoming and OnDemand Webinars View full list

New in Core Data and iOS 8: Batch Updating

Robert Edwards

Can’t get enough info about iOS 8? Join us for our one-day iOS 8 bootcamps in cities across the U.S.

Core Data has had a polarizing effect within the development community. You’d be hard pressed to meet a Cocoa developer who is completely ambivalent to the topic. I won’t mask my opinion: I am a true fan.

I believe that Apple has made significant improvements to the framework each year. This year was no exception, as two highly specialized APIs were introduced. NSBatchUpdateRequest and NSAsynchronousFetchRequest, while somewhat esoteric, were introduced to combat specific issues developers encounter with the framework. And each will come to be invaluable APIs to the serious Core Data developer.

In this first installment, we will be tackling the NSBatchUpdateRequest.

Batch Updating

An often-cited shortcoming of Core Data is its inability to efficiently update a large number of objects with a new value for one or more of its properties. Traditional RDBMS implementations can effortlessly update a single column with a new value for thousands upon thousands of rows. With Core Data being an object graph, updating a property is a three-step process that involved pulling the object into memory, modifying the property and finally writing the change back to the store. Iterating these steps across thousands of objects leads to long wait times for the user, as well as increased memory and CPU load on the device.

The Request

The NSBatchUpdateRequest allows you to go directly to the store and modify your records in a manner similar to traditional databases, rather than with an object graph.

Creating a batch request starts off the same way it does with a standard fetch request. An entity name must be supplied to indicate the type of objects being fetched. As always, an NSPredicate can be supplied to return a limited subset of your entities.

In most cases, you will be dealing with a single NSPersistentStore; however, you do have the option of supplying an NSArray to the affectedStores property if needed.

The additional required property, unique to NSBatchUpdateRequest, is propertiesToUpdate. Here an NSDictionary containing key/value pairs will be used to set the properties with their new values. The keys are NSStrings representing the property names, and the values can be any NSExpression.

Here is an example of various expressions for values:

batchRequest.propertiesToUpdate = @{@"lastVisitDate": [NSDate date],
                                    @"activeThisMonth": @(YES),
                                    @"lastVisitLocation": @"BNR IGHQ"};

Result Types

You have a few options when it comes to the format of a confirmation you receive after applying the batch update.

  • NSStatusOnlyResultType includes only a success or failure status
  • NSUpdatedObjectsCountResultType is the count of modified objects
  • NSUpdatedObjectIDsResultType is an array of NSManagedObjectIDs. In some situations, this result type is necessary, as you will see below.

Example

Say we want to update all unread instances of MyObject to be marked as read:

NSBatchUpdateRequest *req = [[NSBatchUpdateRequest alloc] initWithEntityName:@"MyObject"];
req.predicate = [NSPredicate predicateWithFormat:@"read == %@", @(NO)];
req.propertiesToUpdate = @{
	@"read" : @(YES)
};
req.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *res = (NSBatchUpdateResult *)[context executeRequest:req error:nil];
NSLog(@"%@ objects updated", res.result);

Our batch request is initialized with the entity name MyObject and then filtered for only unread items. We then create our propertiesToUpdate dictionary with a single key/value pair for the property read and constant NSExpression value YES. In this case, we are interested only in a count of updated values, so we use the resultType NSUpdatedObjectsCountResultType.

Speed Comparison

Using Mark Dalrymple’s trusty BNRTimeBlock, we can see that a batch update on 250,000 objects takes just over one second, while an update that involves iterating over each item takes around 16 seconds for the same number of objects.

(Note: All tests were run using XCode 6 Beta 7, along with the iPhone 5s Simulator on a late-2013 MacBook Pro.)

Time Comparison

Memory Comparison

In additon to speed considerations, memory usage on the batch update side is significantly lower. By monitoring the heap size in Xcode, we can observe a batch update increasing the memory footprint by only a few MBs during the fetch. Contrast that with an update that iterated over each object, which resulted in a sharp increase of memory load close to 200 MBs!

Memory Comparison

What’s the catch?

With results that are far more efficient than iterating each object, you might guess there are some tradeoffs. As mentioned earlier, this type of operation is something database systems handle easily, while object graphs stumble. As such, it’s important to understand that when performing a batch operation, you’re essentially telling Core Data “Step aside, I got this”. Apple even equates the batch operation to “running with scissors”.

Here are a few things to keep in mind:

Property Validation: Property validation will not be performed on your new values. Because of this, you can inadvertently introduce bad data into your store. Furthermore, your bad data could go unnoticed until the next time your user makes a modification preventing them from being able to save. In order to mitigate this risk, always sanitize your data prior to executing the update.

Referential Integrity: Referential integrity will not be maintained between related objects. As an example, setting an employee’s department will not in turn update the array of employees on the department object. In light of this, its typically best to avoid using a batch update request on properties that reference other NSManagedObjects.

Reflecting Changes in UI: NSManagedObjects already in an NSManagedObjectContext will not be automatically refreshed. This is where using the NSUpdatedObjectIDsResultType is required if you want to reflect your updates in the UI. With the modified NSManagedObjectIDs in hand, you can refresh the objects as needed:

[objectIDs enumerateObjectsUsingBlock:^(NSManagedObjectID *objID, NSUInteger idx, BOOL *stop) {
    NSManagedObject *obj = [context objectWithID:objID];
    if (![obj isFault]) {
        [context refreshObject:obj mergeChanges:YES];
    }
}];

Merge Conflicts: You must ensure that your NSManagedObjectContext has a merge policy, as you can potentially introduce a merge conflict.

What’s Next

Batch updating provides a seamless way of bypassing Core Data’s convenient, albeit expensive, features. When you need to update a large set of data, this new API allows you to accomplish the task more quickly and efficiently. I know this will become an invaluable tool in my Core Data repertoire!

Stay tuned for my next installment detailing the NSAsynchronousFetchRequest.


*Editor’s note: Robert will discuss Core Data in our live iOS 8 demo Wednesday, Sept. 17, at 2 p.m. EDT. He will be joined by Nerds discussing Interactive Playgrounds and Functional Programming, Asynchronous Testing, Extensions, Cloud Kit, HomeKit, HealthKit and Handoff, along with Adaptive UI and what iOS 8 means for design.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project