Dec 022011
 

NOTE: This post has been superseded by Simplifying Core Data Part II.

RHManagedObject

RHManagedObject is a library for iOS to simplify your life with Core Data. It was motivated by the following:

  • Core Data is verbose. Have a look at Listing 1 from the Apple Documentation and you’ll see it takes ~14 lines of code for a single fetch request. RHManagedObject reduces this to a single line.

  • Each managed object has an object context associated with it, and for some operations you must first fetch the object context in order to operate on the object. For example:

    NSManagedObjectContext *moc = [myManagedObject managedObjectContext];
    [moc deleteObject:myManagedObject];
    

    This is more verbose than necessary since it introduces the object context when its existence is implied by the managed object. RHManagedObject simplifies the above code to:

    [myManagedObject delete];
    
  • Core Data is not thread safe. If you wish to mutate your objects off the main thread you need to create a managed object context in that thread, attach a NSManagedObjectContextDidSaveNotification notification to it, and merge the context into the main object context in an observer method on the main thread. Bleh. RHManagedObject does this for you so that you can work with your objects in any thread without having to think about this.

  • A common Core Data design pattern is to pass the managed object context between each UIViewController that requires it. This gets cumbersome to maintain, so RHManagedObject puts all the Core Data boilerplate code into a singleton, which becomes accessible from anywhere in your code. Best of all, the singleton encapsulates the entire NSManagedObjectContext lifecycle for you: constructing, merging, and saving (even in a multi-threaded app) such that you never need to interact with NSManagedObjectContext directly. RHManagedObject lets you focus on your objects with simple methods to fetch, modify, and save without having to think about NSManagedObjectContext.

  • The generated managed object classes leave little room to add additional methods. You can’t (or shouldn’t) add extra methods to the generated classes since they will be overwritten when the classes are regenerated. RHManagedObject provides a place for additional class and instance methods.

  • Managing multiple models becomes tricky with the standard Core Data design pattern. RHManagedObject (since v0.7) supports multiple models to make this simple and transparent.

Overview

This brief overview assumes you have some experience with Core Data.

A typical Core Data “Employee” entity (say, with attributes firstName and lastName) has an inheritance hierarchy of:

NSObject :: NSManagedObject :: EmployeeEntity

RHManagedObject changes this to:

NSObject :: NSManagedObject :: RHManagedObject :: EmployeeEntity :: Employee

You’ll notice that the RHManagedObject and Employee classes have been added to the hierarchy. The RHManagedObject class adds generic methods (i.e., not specific to your model) that simplifies interacting with Core Data. Its main features are:

  • It manages the object context.
  • It adds easier methods for fetching, creating, cloning, and deleting managed objects.
  • It provides a simplified interface for saving the context, and works the same regardless from which thread it’s called.

For example, the +newEntity method introduced in RHManagedObject lets you create a new managed object with a single line:

Employee *newEmployee = [Employee newEntity];

Fetching all employees with first name “John” is also a single line:

NSArray *employees = [Employee fetchWithPredicate:[NSPredicate predicateWithFormat:@"firstName=%@", @"John"]];

The -delete method lets you delete an existing managed object:

[firedEmployee delete];

Changes can be saved with the +commit method, which will handle the merging of contexts from the different threads. In other words, you can call +commit from your thread and forget about it:

[Employee commit];

You’ll notice that none of these examples require direct use of an NSManagedObjectContext instance. That’s handled for you within the library. Of course, a method is available to fetch the object context for the current thread if it’s required:

NSManagedObjectContext *moc = [Employee managedObjectContextForCurrentThread];

How To Get Started

  • Download RHManagedObject.
  • Copy RHManagedObject.h, RHManagedObject.m, RHManagedObjectContextManager.h, and RHManagedObjectContextManager.m into your project.
  • Include the CoreData framework in your project.

Setup

Recall the new object hierarchy from the overview:

NSObject :: NSManagedObject :: RHManagedObject :: EmployeeEntity :: Employee

Your entity class (e.g., EmployeeEntity) is generated by XCode as usual (CMD-N, NSManagedObject subclass, etc.). However, there are a few manual tasks before and after generation.

  • Before generation you must ensure the Class setting on your entity is set to the entity name. That is, open the xcdatamodeld in XCode, select the entity, and set the Class property (at the right) to the entity name. In the employee example this would be EmployeeEntity. Repeat for each entity to be generated.
  • After your entity classes have been generated you must go back to the xcdatamodeld and change the Class property to your entity subclass. In the employee example this would be Employee.
  • The generated classes must be modified to inherit from RHManagedObject instead of NSManagedObject. It’s a small hack, but only requires changing two lines of code (if anyone has an easier way of doing this then please let me know).
  • The entity subclass (e.g., Employee) is created by normal means and is just a normal subclass, but requires two methods to identify which Core Data entity it extends and to which model it belongs. These are used by the RHManagedObject superclass and looks like the following in the Employee example:

    @implementation Employee
    
    // This returns the name of the Entity it extends (basically the name of the superclass)
    +(NSString *)entityName {
        return @"EmployeeEntity";
    }
    
    // This returns the name of your xcdatamodeld model, without the extension
    +(NSString *)modelName {
        return @"SimplifiedCoreDataExample";
    }
    
    @end
    

    However, it’s also the place where additional methods can be added without disrupting the generated entity class. For example:

    -(NSString *)fullName {
        return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
    }
    

Other Features

Populate Store on First Launch

The library contains code to populate the store on first launch. This was motivated by the CoreDataBooks example, and all you have to do is copy the sqlite file generated by the simulator into your project. The library takes care of the rest.

ARC

The library uses ARC as of version 0.8.0.

Lightweight Migration

If possible, RHManagedObject will automatically perform a Lightweight Migration to altered models. If you wish to block the interface or perform other operations when a migration occurs, you can call the RHManagedObject +doesRequireMigration method from the -application:didFinishLaunchingWithOptions: method of your AppDelegate to see if a migration is pending. This must be done before executing anything else that requires Core Data.

Click here for a blog post on performing a Core Data Migration.

RHCoreDataTableViewController

RHCoreDataTableViewController is a UITableViewController subclass that simplifies the use of NSFetchedResultsController. It contains most of the boilerplate code required for the different delegates, but also:

  • handles large updates by calling [tableView reloadData] instead of -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:newIndexPath for each changed object when a large number of changes are pending (currently set to 10 or more);
  • provides methods to add and manage a search bar (see sample project for usage); and
  • automatically manages the insertion and deletion of rows and sections.

You can use the class by subclassing RHCoreDataTableViewController (instead of UITableViewController) and by implementing the following methods:

  • -fetchedResultsController
  • -tableView:cellForRowAtIndexPath:
  • configureCell:atIndexPath:

An example of how this works can be found in the ExampleTableViewController.m file in the sample project.

RHCoreDataCollectionViewController

RHCoreDataCollectionViewController is a UICollectionViewController subclass with a similar motivation as RHCoreDataTableViewController. It implements the NSFetchedResultsControllerDelegate delegate and requires the following methods to be implemented in your subclass:

  • -fetchedResultsController
  • -collectionView:cellForItemAtIndexPath:

The code is based on Ash Furrow’s UICollectionView-NSFetchedResultsController, but has been modified to fit this library.

Thread Containment

RHManagedObject still uses the older style thread confinement pattern to manage contexts in different threads. A beta has been developed to work with nested contexts, but deadlocks in iOS 5.1 has put the approach on hold. You can read about the deadlocking issue here.

Examples

Once you have setup RHManagedObject it becomes easier to do common tasks. Here are some examples.

Add a new employee

Employee *employee = [Employee newEntity];
[employee setFirstName:@"John"];
[employee setLastName:@"Doe"];
[Employee commit];

Fetch all employees

NSArray *allEmployees = [Employee fetchAll];

Fetch all employees with first name “John”

NSArray *employees = [Employee fetchWithPredicate:[NSPredicate predicateWithFormat:@"firstName=%@", @"John"]];

Fetch all employees with first name “John” sorted by last name

NSArray *employees = [Employee fetchWithPredicate:[NSPredicate predicateWithFormat:@"firstName=%@", @"John"] sortDescriptor:[NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES]];

Get a specific employee record

The +getWithPredicate: method will return the first object if more than one is found.

Employee *employee = [Employee getWithPredicate:[NSPredicate predicateWithFormat:@"employeeID=%i", 12345]];

Count the total number of employees

NSUInteger employeeCount = [Employee count];

Count the total number of employees with first name “John”

NSUInteger employeeCount = [Employee countWithPredicate:[NSPredicate predicateWithFormat:@"firstName=%@", @"John"]];

Get all the unique first names

NSArray *uniqueFirstNames = [Employee distinctValuesWithAttribute:@"firstName" withPredicate:nil];

Get the average age of all employees

NSNumber *averageAge = [Employee aggregateWithType:RHAggregateAverage key:@"age" predicate:nil defaultValue:nil];

Fire all employees

[Employee deleteAll];

Fire a single employee

Employee *employee = [Employee get ...];
[employee delete];

Commit changes

This must be called in the same thread where the changes to your objects were made.

[Employee commit];

Completely destroy the Employee model (i.e., delete the .sqlite file)

This is useful to reset your Core Data store after making changes to your model.

[Employee deleteStore];

Get an object instance in another thread

Core Data doesn’t allow managed objects to be passed between threads. However you can generate a new object in a separate thread that’s valid for that thread. Here’s an example using the -objectInCurrentThreadContext method:

Employee *employee = [Employee getWithPredicate:[NSPredicate predicateWithFormat:@"employeID=%i", 12345]];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // employee is not valid in this thread, so we fetch one that is:
    Employee *employee2  = [employee objectInCurrentThreadContext];

    // do something with employee2  

    [Employee commit];
});

I’m stuck. Help!

Check out the included SimplifiedCoreDataExample for a working example. You can also contact me if you have any questions or comments.

Where is this being used?

RHManagedObject is being used with:

Contact me if you’d like your app to be listed.

Contributions

  • James Carlson
  • Mathew Leffler

Contact

Are you using RHManagedObject in your project? I’d love to hear from you!

profile: Christopher Meyer
e-mail: chris@schwiiz.org
twitter: @chriscdn

License

RHManagedObject is available under the MIT license. See the LICENSE file for more info.

  16 Responses to “Simplifying Core Data”

  1. Hi,

    I got to say this looks quite interesting but the only thing that’s stopping me from abandoning the NSFetchedResultsController is that it updates the fetched objects as soon as they are saved. I was wondering, using your framework, is there any way to update fetched objects as soon as the results are saved without performing a new fetch?

    Thanks in advance.

    • Hi Jim,

      The framework is completely compatible with NSFetchedResultsController. This means changes to your Core Data objects via the framework will be sent to the NSFetchedResultsControllerDelegate delegate. Just use the entity name when defining the NSEntityDescription for your controller e.g.,

      NSEntityDescription *entity = [NSEntityDescription entityForName:[Employee entityName] inManagedObjectContext:[Employee managedObjectContext]];

      Perhaps I’ll write another blog post about that. Let me know how it goes…

  2. Have you made any updates since December, 2011?

  3. A few changes to the framework have been made, but the core is still the same. Are you looking for an update or experiencing problems with the version I posted?

  4. Sorry for the dumb? question, but where do I download the source for the framework? I’d like to see what you did to resolve accessing the managed object context from different threads, whether you settled on the solution you proposed on Stack Overflow or something else altogether.

    • The link is in the middle of the blog post (search for “Download”), and I notice now that it’s hard to see with the theme I’m using. I’ll put the link in a more prominent place after writing this. Let me know how it goes!

  5. Exactly what I was looking for mate. Saved me the time of putting together one for myself. Thanks for your hard work!

  6. I’ve been using your framework and I had one question. When doing a commit on another thread besides the main thread it merges those changes back into the main thread, but what about the other threads that have a context created? Should the changes be merged into those as well?

    • Hi Tavis, I’m not certain I understand the question. The framework automatically handles the creation, merging and destruction of your contexts regardless of which thread you’re on. For example, if you mutate an RHManagedObject off the main thread and call commit, the framework will automatically know it’s not on the main thread and take the appropriate steps to merge the changes into the main context. All you must do is be certain the mutation events and commit call are on the same thread.

      Does that help? Otherwise, please restate your questions and I’ll do my best.

      • Sorry for not being clear. Mainly I’m wondering you’re concerns if a line like this was added:


        [[[self managedObjectContexts] allValues] makeObjectsPerformSelector:@selector(mergeChangesFromContextDidSaveNotification:) withObject:saveNotification];

        After this line:


        [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:saveNotification];

        We have a situation where one thread is saving data to CoreData and another thread comes in and loads the data every five minutes. The thread that is loading was created before the saving thread so I’m trying to find a way to keep the data in sync.

        • I see what you’re trying to do, but I haven’t considered this case nor am I certain if it’s valid. The one possible problem with your code is the makeObjectsPerformSelector: call, which is being called on the main thread. Wouldn’t this need to be called in the thread of each context?

  7. Hi,

    I found your code and it looks quit nice 🙂
    But one Question:
    Can it handle Background Threads correct? I would like to import data from a webservice in a background thread and write it to the core data db.
    I’m using gdc for my thread handling.

    Thanks,
    Stefan

    • Hi Stefan: Yes, threading was one of the motivations of writing this framework. You can commit the changes from any thread and the framework will handle the proper merging into the main thread context.

 Leave a Reply

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)