If you follow me on Twitter, you’re probably aware of a really nasty problem I encountered with versioned Core Data models. Suffice it to say that was two days of hell caused by a single Fetch Request in a .xcdatamodeld file.
The irony of it all is that I no longer use Fetch Requests this way: the queries defined in the model file weren’t being used so I was happy to delete them all.
In my opinion, fetch requests that are stored as a part of the managed object model have some downsides:
- You need to carry around an instance of NSManagedObjectModel in order to access the requests.
- A text-based search (such as Find in Workspace from Xcode’s Search Navigator) doesn’t find the requests.
Instead, I use a category for NSManagedObjectContext that adds some nice functionality for creating fetches and executing them purely with code. The original idea and implementation comes from Matt Gallagher.
A picture’s worth a thousand words, so let’s look at how this works in code. Instead of creating an “existingPartner” fetch configured as:
Fetch all: Partner objects where: Expression: name == $NAME
I write this code:
NSArray *objects = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" withPredicateFormat:@"name == %@", name];
If I need the managed objects in sorted order, there’s this variant:
NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]; NSArray *objects = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" usingSortDescriptors:sortDescriptors withPredicateFormat:@"name LIKE %@", pattern];
Getting count entity objects is just as easy:
NSNumber *salaryLevel = [NSNumber numberWithInteger:1000000]; NSUInteger count = [managedObjectContext countForEntityName:@"Employee" withPredicateFormat:@"salary > %@", salaryLevel];
After reading about how NSExpression can be used to significantly speed up fetches against attribute values, I extended Matt’s NSManagedObjectContext+FetchAdditions category. This lets you do things like this:
NSNumber *marketingPayroll = [managedObjectContext fetchObjectArrayForEntityName:@"Employee" usingAttribute:@"salary" andFunction:@"sum:" withPredicateFormat:@"department == %@", marketing];
One thing to keep in mind when you’re using NSExpression against a SQLite store: it only works on data that has been persisted. Since the underlying implementation relies on SQL functions such as min(), max() and sum(), any managed objects that you have in memory (because the context hasn’t received a -save) won’t be included in the results.
One final trick I use is to keep the code for these fetches as close to the objects and attributes as possible. I’ve found it very handy to create class methods in the NSManagedObject subclasses. For example, using our partner name example from above, I’d implement the following class method for the Partner object:
+ (Partner *)fetchInManagedObjectContext:managedObjectContext withName:(NSString *)name { Partner *result = nil; NSArray *results = [managedObjectContext fetchObjectArrayForEntityName:@"Partner" withPredicateFormat:@"name == %@", name]; if ([results count] > 0) { result = [results lastObject]; } return result; }
Then when I need a Partner with the given name, I use:
Partner *partner = [Partner fetchInManagedObjectContext:managedObjectContext withName:@"CHOCK GLOBAL INDUSTRYS INK"];
I hope this category cleans up your code as much as it has mine. Feel free to use this code in any way you see fit.