Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
In our last installment we covered the NSBatchUpdateRequest
in iOS 8. Today we will outline the NSAsynchronousFetchRequest
, along with its appropriate use cases.
Using Core Data in a multi-threaded environment has always required a deep understanding of the framework and a healthy dose of patience. Asynchronous fetching is not necessarily a feature of a multi-threaded Core Data environment. However, its ability to alleviate a common performance issue could potentially free you up from the added complexity of a multi-threaded CoreData stack.
First, ask yourself the following questions:
If you have answered “Yes” to these questions, an NSAsynchronousFetchRequest
may be all you need to remedy your performance woes. As an example, an application with mostly pre-canned data that requires computationally expensive activities, such as sorting, filtering or charting, would be a good candidate for this approach.
Setting up a separate context and thread for Core Data allows you to perform any necessary operations (Fetching, Inserting, Deleting, Modifying) on your managed objects on a background thread. This will free up your main thread for smooth user interaction. However, even on that background thread, each operation will block the context while it’s being performed.
An NSAsynchronousFetchRequest
allows you to send off your fetch request to the NSManagedObjectContext
and be notified via a callback block when the objects have been populated in the context. This means that, whether on the main thread or a background thread, you can continue to work with your NSManagedObjectContext
while this fetch is executing.
NSAsynchronousFetchRequest
uses NSProgress
to allow you to get incremental updates on how many objects have been fetched via Key-Value Observing (KVO). The only catch with incremental notifications is that because database operations occur in streams, there is no upfront way for the NSAsynchronousFetchRequest
to know exactly how many objects will be returned. If you happen to know this number in advance, you can supply this figure to the NSProgress
instance for incremental updates.
An added benefit of using NSProgress
is being able to give the user the ability to cancel a long-running fetch request.
Continuing our unread items example from earlier, lets see how an asynchronous fetch with progress updates would look. Assuming we have pre-computed the number of unread items, our fetch request would look like this:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"MyObject"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"read == %@", @(NO)];
NSPersistentStoreAsynchronousFetchResultCompletionBlock resultBlock = ^(NSAsynchronousFetchResult *result) {
NSLog(@"Number of Unread Items: %ld", (long)result.finalResult.count);
[result.progress removeObserver:self
forKeyPath:@"completedUnitCount"
context:ProgressObserverContext];
[result.progress removeObserver:self
forKeyPath:@"totalUnitCount"
context:ProgressObserverContext];
};
NSAsynchronousFetchRequest *asyncFetch = [[NSAsynchronousFetchRequest alloc]
initWithFetchRequest:fetchRequest
completionBlock:resultBlock];
[context performBlock:^{
//Assumption here is that we know the total in advance and supply it to the NSProgress instance
NSProgress *progress = [NSProgress progressWithTotalUnitCount:preComputedCount];
[progress becomeCurrentWithPendingUnitCount:1];
NSAsynchronousFetchResult *result = (NSAsynchronousFetchResult *)[context
executeRequest:asyncFetch
error:nil];
[result.progress addObserver:self
forKeyPath:@"completedUnitCount"
options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
context:ProgressObserverContext];
[result.progress addObserver:self
forKeyPath:@"totalUnitCount"
options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
context:ProgressObserverContext];
[progress resignCurrent];
}];
Here we’ve started with our standard fetch request just as before. We then create an NSPersistentStoreAsynchronousFetchResultCompletionBlock
that will be executed once the asynchronous fetch completes. This block will log the final count of returned objects to the console, as well as remove ourselves as an observer of the incremental progress updates. We assemble these two pieces together to create our NSAsynchronousFetchRequest
.
Now, it is time to begin executing our fetch request. We first call the NSManagedObjectContext
method performBlock
to ensure our operation is dispatched on the correct queue. Before kicking off the request, we create an NSProgress
instance with our pre-computed unread item count as the totalUnitCount
. Immediately after we execute the asynchronous request, we are returned an NSAsynchronousFetchResult
“future” instance. This object can be used to grab the fetch result’s NSProgress
instance for observing incremental updates. Lastly, we make sure to call resignCurrent
on our NSProgress
instance so that the NSAsynchronousFetchResult
’s NSProgress
instance can take over.
The only thing left to do is to observe the KVO notifications being broadcast by the NSProgress
instance for progress updates.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == ProgressObserverContext) {
if ([keyPath isEqualToString:@"completedUnitCount"]) {
NSNumber *newValue = change[@"new"];
NSLog(@"Fetched %@ objects", newValue);
} else if ([keyPath isEqualToString:@"totalUnitCount"]) {
NSNumber *newValue = change[@"new"];
NSLog(@"Finished with %@ objects fetched", newValue);
}
}
}
Asynchronous fetching enables a lightweight means to executing a long-running fetch without grinding the system to a halt, or getting tangled in a complex multi-threaded universe.
While I don’t think NSAsynchronousFetchRequest
or NSBatchUpdateRequest
, on their own, will be winning over Core Data detractors, it does show Apple’s continued improvement of the framework. Each feature attacks a specific pain point that Core Data enthusiasts have encountered in the past.
Be sure to check out the accompanying sample project detailing both of these features over on GitHub. And if you want to learn iOS 8 with us, check out our one-day iOS 8 bootcamps in cities across the U.S.
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...