Who of you Cocoa devs likes Core Data? Hands up... Whoa, I can see many hands up there. I share the same sentiment. For those of you who don't know what Core Data is don't bother reading further but here is a short introduction taken out from the docs:
The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence. Its features include:t
- Built-in management of undo and redo beyond basic text editing
- Automatic validation of property values to ensure that individual values lie within acceptable ranges and that combinations of values make sense
- Change propagation, including maintaining the consistency of relationships among objects
- Grouping, filtering, and organizing data in memory and in the user interface
- Automatic support for storing objects in external data repositories
- Optional integration with Cocoa bindings to support automatic user interface synchronization
Fetching objects from persistent stores! That sounds nice. How does a typical fetch look like? Again taken out from the docs:
NSManagedObjectContext *context = <#Get the context#>;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"<#Entity name#>" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"<#Sort key#>" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"<#Predicate string#>",
<#Predicate arguments#>];
[request setPredicate:predicate];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle error
}
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
Unfortunately Core Data currently doesn't have asynchronous fetch support built in. So if you have a large data set and/or have a complicated fetch request it takes time until it fetches your objects from the persistent stores. But most importantly the current thread blocks while the context executes the fetchRequest. If that thread is the main thread then your UI will hang and gives the impression to the user that the app has frozen. We don't want that, do we?
I've created IZManagedObjectContext, a subclass of NSManagedObjectContext, that extends it with asynchronous fetch feature. Let me modify the above snippet and show you have to use it:
IZManagedObjectContext *context = <#Get the context#>;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"<#Entity name#>" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"<#Sort key#>" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"<#Predicate string#>",
<#Predicate arguments#>];
[request setPredicate:predicate];
[context executeFetchRequestAsynchronously:fetchRequest delegate:delegate];
When the fetch is complete the the delegate will be notified with
- (void)managedObjectContext:(IZManagedObjectContext *)context fetchCompletedForRequest:(NSFetchRequest *)request withResults:(NSArray *)results error:(NSError *)error;
Note that the difference between the two fetch methods is that the asynchronous one does NOT block the thread so your app looks snappy. That makes us happy.
If you are anxious to try it out and don't want to know the juicy details how it gets the job done then just head over here. I have licensed it under the BSD license.
How does it work, you ask?
It follows these guidelines. It fetches the objectIDs (which are immutable and safe to pass across thread boundaries) that satisfy the predicate of the fetch request on a separate thread using an NSOperation on a separate NSManagedObjectContext instance using the same persistentStoreCoordinator. Then it passes those objectIDs back the the main thread and issues a new fetch request with a predicate that asks for those specific objects with the objectIDs that we received. But I said it doesn't block the main thread? Well, I wasn't telling the truth. For my defense the second fetch should have only O(n) complexity if the executeFetchRequest:error: is optimized for fetching objectIDs. But in most of the cases this second fetch will execute faster than the original fetch request which had a more "complicated" predicate. And you really should be using a fetch limit when dealing with large data sets anyway.
I haven't included any example project that shows IZManagedObjectContext in action. You just have to take my word for it that it works. I use it in the latest alpha build of OpenMaps and haven't encountered any problems, yet. If you find bugs in it then please do notify me.
![Reblog this post [with Zemanta]](http://img.zemanta.com/reblog_e.png?x-id=cdb39923-e501-46cc-8f20-4565cffa8271)
7 comments: