Monday, June 27, 2011

Organising Core Data for iOS

I rarely work on an iOS project that doesn't use Core Data for persistent data management. Over time I have settled on a strategy for organising Core Data and its Managed Object classes, which I will describe in this post. This information will hopefully be useful to those new to Core Data wondering how to organise things, as well as those more experienced interested in how other iOS devs work with Core Data.


I have created a little demo iPhone app that goes along with this post.  You can get it from github. The app demonstrates how I organise and use Core Data in a real-world (although very simplified) app and demonstrates how to wire Core Data to table views, including automatically updating table content (with animation) when Core Data changes occur.


Model Layout


Let's start by defining a very simple model. Assume we are building an app to track people's DVD collections (remember DVDs? If you are like me you ended up with a big pile of them gathering dust in a cupboard somewhere). We will define two entities, "Person" and "DVD", and link them together.



Each person may own many DVDs so there is a one-to-many relationship between Person and DVD. The "username", "name" and "title" attributes are strings and "purchaseDate" is of type Date.


NSManagedObject Subclasses


After laying out the model I then select each Entity and customise the Class name. This field sets the name of the Managed Object (NSManagedObject) subclass that will be tied to the Entity.  I have standardised on using the Entity name prefixed with "MO". So for Person I set a Class name of "MOPerson" and for "DVD" I set "MODVD".


Actual code for the NSManagedObject subclasses are created by selecting the Entities and choosing the "Editor / Create NSManagedObject Subclass..." menu item.




I organise all my model code into a "Model" group and place the auto created NSManagedObject subclasses in a "Managed Objects" sub group. Note that after creating the NSManagedObject subclass files I never touch them. I consider them to be "owned" by Core Data. The main advantage of this strategy is that I can change the model layout and re-generate the NSManagedObject subclasses at any time during development, without risk of losing customisations.


Model Interface


My general rule of thumb is to encapsulate all the Core Data queries in the model code. The rest of the project accesses the model via this interface and generally doesn't talk to Core Data directly (other than working with the Managed Object subclasses). This helps keep the "M" separate from the "V" & "C".


So, if I don't touch the NSManagedObject subclass files how do I customise the interface?  I use our good friend the Objective-C Category. I define a "Management" category for each Managed Object that provides the API for the rest of the project to work with, including inserting, fetching and deleting records from the database.


Furthermore, to keep the Management category methods as short and simple as possible (aiming for DRY) I define a bunch of helper functions containing the common Core Data query logic. See ModelUtil.h/ModelUtil.m for these convenience functions.  You'll see that they centralise access to the "default" Managed Object Context, which is the NSManagedObjectContext on the main thread created by the application delegate if you use Apple's application templates.  They are mostly very simple functions, but are reusable across projects and reduce repetition.
With all that in place the NSManagedObject category methods are written specifically for the application needs. For the demo project MOPerson (Management) contains class methods to insert and retrieve MOPerson instances. The implementation looks like:


static NSString *entityName = @"Person";

@implementation MOPerson ( Management )

+ (MOPerson *)insertPersonWithUsername:(NSString *)username
                                  name:(NSString *)name
                  managedObjectContext:(NSManagedObjectContext *)moc
{
    MOPerson *person = (MOPerson *)[NSEntityDescription
                                    insertNewObjectForEntityForName:entityName
                                    inManagedObjectContext:moc];
    person.username = username;
    person.name = name;
    
    return person;
}

+ (MOPerson *)insertPersonWithUsername:(NSString *)username
                                  name:(NSString *)name
{
    NSManagedObjectContext *moc = defaultManagedObjectContext();
    return [MOPerson insertPersonWithUsername:username
                                         name:name
                         managedObjectContext:moc];
}

+ (MOPerson *)personWithUsername:(NSString *)username
{
    NSPredicate *predicate = [NSPredicate
                              predicateWithFormat:@"username == %@",
                              username];
    
    NSArray *sortDescriptors = [NSArray arrayWithObject:
                                [NSSortDescriptor sortDescriptorWithKey:@"username"
                                                              ascending:YES]];
    
    MOPerson *person = (MOPerson *)fetchManagedObject(entityName,
                                                      predicate,
                                                      sortDescriptors,
                                                      defaultManagedObjectContext());
    
    return person;
}

@end

In our application code, fetching the MOPerson instance for a particular username is as simple as:

MOPerson *person = [MOPerson personWithUsername:username];

which will return nil if not found.


Inserting a new Person instance in the database is one line followed by a request to the Managed Object Context to commit all changes:

MOPerson *person = [MOPerson insertPersonWithUsername:username name:name];

if (!commitDefaultMOC()) {
    // handle error
}

In a more interesting project the Management categories may also contain instance methods to customise attribute access or validation, for example.


To see a working example of all this get the demo app source code.


Summary


This is how I organise Core Data in my iOS projects. None of this is rocket science but standardising on a strategy for organising Core Data will help keep your projects tidy and maintainable. I am interested to see how other iOS devs organise Core Data in their projects so I can continue to improve my practice. In future posts I will share some more of my experiences with Core Data on iOS.

3 comments:

  1. Wonderful work. Certainly appreciated. I am a novice to Objective-C/Cocoa -- coming from a 15 year career in MS SQL and C#/.NET... this was a very insightful post!

    ReplyDelete
  2. Love this approach of managing core data! I'm already using this in my current application, and will keep using it in my future applications.

    Quick question.
    How do you integrate iCloud sync and backup to this.

    would you mind making a tutorial/ sample code showing how to achieve this.

    It would really Help.

    Let me know.

    Thanks :D

    ReplyDelete
  3. When I get a chance I will try to write up an example of using iCloud.
    Cheers.

    ReplyDelete