This is an Objective-C category for Core Data (NSManagedObjectContext (EasyFetch)
) that offers a few useful functions added that simplify Core
Data programming for Mac OS X and iPhone OS. It's based loosely on
code by Matt Gallagher, but with several enhancements and modifications
that I needed for a project I was writing that used Core Data.
This category is released under the MIT license.
Copyright © 2009, 2010 Austin Ziegler
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
Add the files to your Xcode project:
NSManagedObjectContext-EasyFetch.h NSManagedObjectContext-EasyFetch.m
-
Import the header in an implementation file that uses Core Data where you need the functionality:
#import "NSManagedObjectContext-EasyFetch.h"
-
Call one of the category methods:
[[self managedObjectContext] fetchObjectsForEntityName:@"Employee" predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)", minimumSalary];
There are several methods added in the EasyFetch
category, but they're all
variations on two basic options for a query, which simplifies understanding
them.
- Should the objects returned be filtered?
- Should the objects returned be sorted?
Accordingly, there are four categories of methods:
- All objects, unsorted
- All objects, sorted
- Some objects, unsorted
- Some objects, sorted
All methods return an NSArray
of objects returned by the query. Any
exceptions thrown are unhandled by the category.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName;
Returns all objects from an entity in Core Data's natural (unsorted) order. Use only when you don't care about the order in which objects are processed.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortWith:(NSArray*)sortDescriptors;
Returns all objects from an entity, sorted by the NSArray
containing
NSSortDescriptor
objects.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortByKey:(NSString*)key
ascending:(BOOL)ascending;
Returns all objects from an entity, sorted by the key value (which must be an
attribute in the entity). Creates an NSSortDescriptor
object based on the
key
and ascending
values.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
withPredicate:(NSPredicate*)predicate;
Returns the objects in an entity that match the predicate comparison rules in
natural order. Use this version and construct your NSPredicate
value
appropriately for predicate parameters that contain user input.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
predicateWithFormat:(NSString*)predicateFormat, ...;
Returns the objects in an entity that match the constructed predicate, in
natural order. An NSPredicate
will be created using [NSPredicate predicateWithFormat:arguments:]
. Fast and easy, but unsafe for values that
involve user input.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortWith:(NSArray*)sortDescriptors
withPredicate:(NSPredicate*)predicate;
Returns the objects in an entity that match the predicate comparison rules,
sorted by the NSArray
containing NSSortDescriptor
objects. Use this version
and construct your NSPredicate
value appropriately for predicate parameters
that contain user input.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortByKey:(NSString*)key
ascending:(BOOL)ascending
withPredicate:(NSPredicate*)predicate;
Returns the objects in an entity that match the constructed predicate, sorted
by the key value (which must be an attribute in the entity). Creates an
NSSortDescriptor
object based on the key
and ascending
values. Use this
version and construct your NSPredicate
value appropriately for predicate
parameters that contain user input.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortWith:(NSArray*)sortDescriptors
predicateWithFormat:(NSString*)predicateFormat, ...;
Returns the objects in an entity that match the predicate comparison rules,
sorted by the NSArray
containing NSSortDescriptor
objects. An NSPredicate
will be created using [NSPredicate predicateWithFormat:arguments:]
. Fast and
easy, but unsafe for values that involve user input.
- (NSArray*)fetchObjectsForEntityName:(NSString*)entityName
sortByKey:(NSString*)key
ascending:(BOOL)ascending
predicateWithFormat:(NSString*)predicateFormat, ...;
Returns the objects in an entity that match the constructed predicate, sorted
by the key value (which must be an attribute in the entity). Creates an
NSSortDescriptor
object based on the key
and ascending
values. An
NSPredicate
will be created using [NSPredicate predicateWithFormat:arguments:]
. Fast and easy, but unsafe for values that
involve user input.
As stated at the beginning, this is loosely based on code by Matt Gallagher, where he writes:
It's a lot easier to get your data out of Core Data than the documentation will tell you. This simple 1-line fetch will work just as well as Apple's suggested 10-line approach for most uses.
He points out that the Core Data Programming Guide gives a multi-line approach to fetching data from a Core Data entity:
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Employee"
inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *minimumSalary = ...;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)",
minimumSalary];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
Matt's sample code simplifies the above fetch to a single line:
[[self managedObjectContext] fetchObjectsForEntityName:@"Employee"
withPredicate:@"(lastName LIKE[c] 'Worsley') AND (salary > %@)",
minimumSalary];
When I found this, it simplified my Core Data code significantly, but it turned
out to be insufficient for my purposes. Not only did I need filtering, I needed
sorting; I also had a couple of small cases where I just needed everything (the
equivalent of SELECT * FROM Employee
in SQL).
Matt mentioned that his pattern of using id
as the type of the
withPredicate:
parameter was questionable design. Because it was id
, he
could do some checking inside fetchObjectsForEntityName:withPredicate:
to
determine whether the parameter was an NSPredicate
or an NSString
to be
converted into an NSPredicate
with predicateWithFormat:arguments:
. I've
written similar code in Ruby, but it didn't feel right for Objective-C. It also
made the string-formatted predicate too attractive for cases where your query
parameters might have been based on user input.
By time I was done adding the features I needed, I had changed this design to
be an explicit choice among multiple methods. Now, the resulting category
offers an explicit NSPredicate
form (e.g.,
fetchObjectsForEntityName:withPredicate
) and a NSString
-formatted form
(e.g., fetchObjectsForEntityName:predicateWithFormat
). It is easier to use
the predicateWithFormat
methods, but it is safer to use the withPredicate
methods.
Finally, Matt's code returned NSSet
, but this turned out to be a problem for
my data, so I am returning NSArray
instead; it's trivial to add [NSSet setWithArray:results]
at the end to convert the returned data to a set if you
require an NSSet
instead of an NSArray
.