Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
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
.
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 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 NSString
s 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"};
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 statusNSUpdatedObjectsCountResultType
is the count of modified objectsNSUpdatedObjectIDsResultType
is an array of NSManagedObjectID
s. In some situations, this result type is necessary, as you will see below.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
.
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.)
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!
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 NSManagedObject
s.
Reflecting Changes in UI: NSManagedObject
s 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 NSManagedObjectID
s 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.
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.
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...