Super Happy Easy Fetching in Core Data
First up, I want to thank Matt Long and Marcus Zarra for allowing me to guest post on CIMGF. This post is the first in a short series of topics describing how to I’ve made using Core Data a little simpler without giving up the power features you still need. The full project from which this series is derived is available on github.
Core Data, for both iPhone and Mac, is a very powerful framework for persisting your objects out of memory, and into a more permanent storage medium. With the enormous power of Core Data, it can be easy to slip into the trap of thinking that Core Data is very complex.
Easy Fetches
When I was first learning about Core Data, I naively thought that getting data from a data store would be easy. Core Data is a framework for fetching and persisting data, after all! I eventually learned that the minimum code required in order to fetch data from the store resembled this:
NSManagedObjectContext *context = [[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *searchFilter = [NSPredicate predicateWithFormat:@"attribute = %@", searchingFor];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
This is quite a bit of code for pulling data out of a store, and this doesn’t even include error handling. I’ve often seen this code copied and pasted where needed in other projects. Some developers even use code snippets to generate these fetch requests. All that similar code can easily become a nightmare to maintain once your app starts to contain many entities. What I wanted was a little more automagic.
About a year ago, I ran across this post by Matt Gallagher on his terrific blog Cocoa with Love, which introduced the idea of the “one line fetch”. The blog post had a good idea, and a simple example of how to implement such an idea, however it occurred to me there were some ways I could enhance the example. The first was the use of the entity name in the method as a string parameter. This is required for the case that there are no custom NSManagedObject subclasses for your entities. And second, the example still required the helper methods to be part of your view controllers.
One reason to use helper methods to generate your fetch requests is that your code remains dry. That is, you remove duplication. Once you start using Core Data, you quickly realize that many requests start to look alike after a while. By consolidating this logic into a single method, or set of methods, we can be sure that optimizing one request can improve many requests. But perhaps more important is that by condensing the code, and removing the repetitive parts, you can easily see at a glance what the code intends to do, with the details only a method call away.
Dynamically Generating your Fetch Requests
In order to request data from your Core Data store, you must start with an NSFetchRequest. As the sample request above showed, we’ll need to at least set the entity for the data we’re intending to fetch.
I always use mogenerator to generate custom subclasses for my Core Data entities. With each custom subclass, you get some valuable information that can help ease the process of building helper methods to autogenerate fetch requests. The primary bit of useful information is a Class object that is related to your Entity. If your entity names and class names are identical, it’s fairly easy to derive a class method to create the correct NSEntityDescription at runtime by using the NSStringFromClass() function. However, with mogenerator, a convenience method that returns the entity description required for fetch requests is auto generated for you called entityInManagedObjectContext:.
When you have a class method, the self object refers to the current Class object, from which you can get a string to look up the proper entity description required for a lookup. This is a big step in autogenerating requests. In the case of mogenerated code, we can check if the current Class responds to a particular selector, entityInManagedObjectContext:, and call that instead. For now, assume that the following methods are class methods in an NSManagedObject subclass called MyEntity.
+ (NSEntityDescription *)entityDescriptionInContext:(NSManagedObjectContext *)context
{
return [self respondsToSelector:@selector(entityInManagedObjectContext:)] ?
[self performSelector:@selector(entityInManagedObjectContext:) withObject:context] :
[NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context];
}
Using this method, we can dynamically determine the entity for which we should fetch data. So, let’s start by creating a class method to find all instances of a particular entity in the store:
+ (NSArray *)findAllObjects
{
NSManagedObjectContext *context = [[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
return [context executeFetchRequest:request error:nil];
}
And using this method from, for example a View Controller in our app, we could simply call:
[MyEntity findAllObjects];
The niceness of this syntax over the fully verbose glob is that at a glance I can quickly see what’s going on. In this case, I want all the instances of MyEntity in the store, in no particular order. Simple and to the point without having to decipher a series of NSFetchRequest customizations (which would be minimal for this example). In this case, we’re passing a message to the class method on MyEntity.
Next, we need to set some default error handling, and we have a simple way to find all objects in a store that are MyEntity instances.
+ (NSArray *)findAllObjects
{
NSManagedObjectContext *context = [[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
}
return results;
}
I’m not explicitly adding error handling here, however inspecting the error object will give you some useful information to track down Core Data issues, such as validation errors, that might prevent your data from being saved. But there are two problems here. First, we don’t always want to use the default context from the Application Delegate. Second, and more importantly, we should be able to use this for other entities in the store.
Addressing the first issue–the matter of specifying the context–this is important because there will be cases where we need to perform some operation on this data in the background on another thread. The proper way to do that in Core Data is to create another context, so let’s make the context a method parameter:
+ (NSArray *)findAllObjectsInContext:(NSManagedObjectContext *)context
{
...
}
and we can still use the simple version of the find method like this:
+ (NSArray *) findAllObjects
{
return [self findAllObjectsInContext:[[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
So, now we have two methods that help find entities in our store, and the context is an injectable dependency. Fancy…and helpful for those cases involving threads.
The other problem was the matter of reusing this method across any custom NSManagedObject subclass. All objects that store data for you in Core Data are instances of or subclasses of NSManagedObject. The easiest way to reuse this method in this case is via a category. However, by placing these methods in a category of NSManagedObject, rather than the MyEntity subclass, all subclasses of NSManagedObject will inherit them at runtime. This means all entities in our store will have this nice helper method for retrieving all instances.
//NSManagedObject+EasyFetching.h
@interface NSManagedObject (EasyFetching)
+ (NSEntityDescription *)entityDescriptionInContext:(NSManagedObjectContext *)context;
+ (NSArray *)findAllObjects;
+ (NSArray *)findAllObjectsInContext:(NSManagedObjectContext *)context;
@end
//NSManagedObject+EasyFetching.m
@implementation NSManagedObject (EasyFetching)
+ (NSEntityDescription *)entityDescriptionInContext:(NSManagedObjectContext *)context;
{
return [self respondsToSelector:@selector(entityInManagedObjectContext:)] ?
[self performSelector:@selector(entityInManagedObjectContext:) withObject:context] :
[NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context];
}
+ (NSArray *)findAllObjects;
{
NSManagedObjectContext *context = [[[UIApplication sharedApplication] delegate] managedObjectContext];
return [self findAllObjectsInContext:context];
}
+ (NSArray *)findAllObjectsInContext:(NSManagedObjectContext *)context;
{
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
}
return results;
}
@end
So now we have a class level find method on any NSManagedObject that will:
- create the fetch request based on the class,
- execute that request
- use the same error handling for all requests (this is handy for logging core data errors to the console)
And, in addition, we now have a place to put any custom helper methods. Since these methods are categories, they won’t interfere with any current fetching code already in your apps. These methods also have the handy side effect of being included in the type-ahead list in Xcode so they are there when you need them.
Next time, we’ll discuss how to handle other common request operations such as filtering, prefetching keys and even returning only one object.
In the second code block, did you mean “respondsToSelector” instead of “respondsToSelected”?
Why the autorelease of NSFetchRequest in “findAllObjectsInContext”? You’ve pulled the results into a variable, you can safely release the fetch request before returning from the method.
@Jeff I’ve updated the sample codes with the correct method. Thanks!
@rounky It could be written with a release after the request has been executed, sure. I prefer to not deal with memory management manually as much as possible. However, I can see the argument that since the request’s usefulness is complete after it has been executed, we should, well, execute it again with a release and free up memory.
I use this code: https://gist.github.com/872056
That way, I can do something along the lines of:
MYObject *object = [MYObject fetchFirst:YES managedObjectWithPredicateFormat:@”parent.roleModel == %@”, [MYEinstein sharedInstance]];
where “MYObject” is a subclass of NSManagedObject. It seems to work reasonably well for me — and it makes me wonder why such a convenience method doesn’t already exist in the SDK, so perhaps there’s something I am missing?
I completely understand and agree with your motivation here. I’ve also been working on a framework with similar goals in mind. One goal in my framework is to hide the NSManagedObjectContext object. I’ve gotten it to a point where I can fetch (also using a class method on the entity), modify, save, and delete managed objects without a handler on a NSManagedObjectContext (that’s all hidden in the framework). Even better, it handles all context merging among threads so you don’t have to. Anyway, details on the work in progress are here:
http://bikepress.tumblr.com/post/3948003272/simplifying-core-data-part-i
In your code you accept a NSManagedObjectContext parameter. Are there instances when you’d want a second NSManagedObjectContext other than when working in a different thread?
@chris In the library referenced at the begining of the article, it also tried to hide the managedobjectcontext as much as possible. There are cases other than threading in which specifying the context in places other than a background thread are useful. The most obvious one that comes to mind is when you want to create objects and not saved them right a way. By having the context as a parameter at the end, you have a really easy way to add the context if you need to.
I’ll have another post or two hopefully explaining some of the conventions I created for the library that should help understand my motivations for the way it was written.
@Saul Thanks, I look forward to reading more about your approach. I appreciate there are others who share concerns about these things.
I never considered that, but it makes a lot of sense now that I think about it. Thanks!
I’ll need to take a closer look at your library. I’m curious how you approach the problem of hiding the managedobjectcontext, especially over different threads.
@Saul: I just looked over your approach. Kudos. We have many similar ideas, but you’ve taken it much further. Well done. I’ve learned much from this.
How copy content from one persistent store to other, need partial load:
bundle1.sqlite -> mainBundle.sqlite
bundle2.sqlite -> mainBundle.sqlite
…
bundlen.sqlite -> mainBundle.sqlite
if bundle1 contain full data about bundle1, and some pointer on next bundle (bundle2), etc.
Please help me,…