Inspired Questions: Simple Databases in iOS

Call me a spoiled .Net developer, but I still find iOS APIs to be a bit laborious. When I get to the end of analyzing the documentation, programming samples, and stack overflow threads to solve simple a problem, I have a consistent feeling of "Wow, I had to use every tool in the toolbox just to turn one screw".

To help other developers who might be facing some of these problems, including myself in the future, I've dropped some simple classes into my GitHub Gists to solve recurring problems. While each existed in some form before starting the project, each one was perfected while building Inspired Questions.

Simple Database

CoreData is one of the most powerful libraries in the iOS developer's arsenal. It provides a visual editor for data models and object-relational mapping (or ORM) to Objective-C objects. Unfortunately, the process to get from the shiny editor to real objects in code involves mastery of a small constellation of classes: NSManagedObjectContext, NSManagedObjectModel, and NSPersistentStoreCoordinator. The SimpleDatabase class seeks to abstract away the details of these three classes, plus offer some convenient functionality common to most apps.

It is intended to serve as a base class a more specific "Database" that offers a smoother facade for your application's data. The sub-class must designate the Data Model by setting the "objectModelName" property, then name the file by setting the "fileName" property. Subsequent data access after setting these fields will result in automatic initialization of the CoreData objects and availability of the database to the app.

The SimpleDatabase class is the logical continuation of the Master-Detail project template's CoreData features in the master ViewController, except that is is all refactored into one file instead of egregiously sprinkled throughout the user-interface.

Using SimpleDatabase

To start using SimpleDatabase, I do the usual CoreData design work: make a new Data Model and create the tables and relationships within it for my app, then generate the NSManagedObject sub-classes.

Then I drop-in the SimpleDatabase class. I create a sub-class of SimpleDatabase that is only accessible as a singleton. Usually, the app requires the ability to create, fetch, and delete instances of every type in the data model. Concrete implementations for a type named "Task" might look like:

- (Task *)createTask
return (Task *)[self create:@"Task"];

- (NSArray *)fetchTasks
return [self fetchAll:@"Task"];

- (Task *)fetchTaskById:(NSString *)taskId
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"taskId == %@",taskId];
return [self fetchOne:@"Task" withPredicate:predicate];

- (void)deleteTask:(Task *)task
[self deleteOne:task];

It should be evident that importing one class and creating short methods for each type is easier than starting from scratch. In the future, some ability to generate these declarations programmatically might be a convenient time-saver (I would consider making a set of macros...if I were not grappling with a puritanical bias against them).

There are also helpers for building NSFetchRequest instances and conveniently converting them into NSFetchedResultsController objects, where you can easily wire them to your UI.

Best Practices for SimpleDatabase

Always access the SimpleDatabase instance from the main thread. Since the SimpleDatabase only creates one NSManagedContextObject, you MUST only access the data from one thread in your app. It should be simple enough to modify the class to setting up many contexts for many threads; however, I've yet to have an app demand it.

Always make the child class of SimpleDatabase a Singleton. If you do make more than one instance for a Data Model in the app, then you will have competing copies of the database trying to save to disk. It's possible that is desirable for your app, but in most cases, there is only one set of data that is used whenever the app is run.

Always try to add model-specific functionality as a Category to the specific model class (NSManagedObject sub-class). Avoid the temptation to burden your database class with model-specific logic whenever possible. Network serialization for the model and model validation are great examples of methods that belong in a Category.