Introduction

I recently attended a session at CocoaConf DC on Core Data given by Walter Tyree, owner of Tyree Apps, LLC, an IT Consulting and iOS software shop located in Alexandria, VA. Core Data is a powerful iOS tool allowing your application to persist data in an efficient way. The most remarkable thing about Core Data is that the API used by the developer remains the same regardless of that backing data store; whether it is SQLite, XML (OS X only), Binary, or an in-memory store. Core Data should be thought of more as an ORM rather than a relational database.

Anyone that has used Core Data has ultimately run into an issue when things change. Whether it is the model updating, upgrading a data store due to an invalid model, or saving data on alternate threads. A lot of code needs to be written to handle these things, and also just to start using Core Data. The template provided by Apple mentions some of these issues in the comments, but does not readily provide solutions to these issues. Magical Record makes vanilla Core Data implementations easy. Magical Record makes many tasks such as creating the persistent store, managed object context, and multi-threaded saving as little as a single line of code. In this post I will walk through a demo application to highlight the benefits of using Magical Record with Core Data. I recommend grabbing the demo project and reading this post while looking at the project in Xcode.

Project Setup

Lets start with the setup of Magical Record. It can be downloaded from https://github.com/magicalpanda/MagicalRecord/tags. I recommend grabbing the latest stable tag, which is 2.1.

  1. Importing Magical Record is a breeze, once you have unpacked the archive simply drag the 'Magical Record' subfolder into your the root of your project directory in Xcode. If you downloaded version 2.1 it will be'Magical Record-2.1/Magical Record'.
  2. Ensure your project has the Core Data framework included.
  3. Add 'CoreData+MagicalRecord.h' to your PCH or AppDelegate file, or any file where Magical Record will be used.
  4. Done. Magical Record is now set up.

Model Setup

Begin by adding a data model file to the project from the New File menu.

Select the new model file to open the model editor. Add a new entity called 'Message' to the model and two attributes for the Message entity:

  • text - String
  • timestamp - Date

This app will simply log messages typed by the user and display them in a table view along with the timestamp. The completed model will look like the image below.

I will also have Xcode generate the Model classes for me.

Also choose the 'Add Model Version' option so that future changes to the model can be migrated. Each time a change will occur to the model, a new model version should be added and changes made to the new version. With this demo app there are no changes to the new version, but its good practice to always work with a versioned model.

Magical Record Setup

Now that we have a Model and corresponding managed objects setup we can start adding code to create the data store. Now, assuming you're managing your CoreData context in the AppDelegate, to create your data store and initialize your managed object context add this line to your AppDelegate:didFinishLaunchingWithOptions:

[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"Messages.sqlite"];

There are 4 other setup options, however this setup option provides the most flexibility, and handles updating existing stores when the model changes, assuming the change can be handled by Core Data's auto-migration utility. Unhandled model changes will cause the existing store to be deleted and a new store created in its place by Magical Record, without Magical Record an incompatible data store can cause a crash or an unusable model, so its important that your changes can be migrated. For more information about what model changes can be handled automatically by Core Data please refer to the Apple Developer Library here.

Now, when the application is run the Messages data store will be created. To ensure the store is persisted, it needs to be saved. There a lot of factors to consider when deciding the frequency with which to save, for this demo I will defer saving until one of the last possible moments, when the app enters the background. Deferring saving until the app enters the background will limit the amount of disk writes we perform, and allow us force a save at will from the simulator. If the data is more important you may want to save immediately. Apple recommends that you defer saving until willTerminate, however this lifecycle method is not called by the simulator. Add this line to AppDelegate:applicationDidEnterBackground:

[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

There is one more Magical Record call needed now, and this will be added to AppDelegate: applicationWillTerminate:

[MagicalRecord cleanUp];

This call simply releases references that were held by the Magical Record singleton class.

App Setup

Now that we have the Core Data stack setup it is time to create the view controller. Below is a shot of the storyboard used for the demo app. The Main View Controller is a UIViewController subclass with a UITableView added. The Message View Controller appears when the '+' button is clicked. This view controller allows the user to add a new Message to the data store.

Magical Record makes adding new objects easy as can be, the code below is called when the 'Add' button is clicked.

Message *message = [Message MR_createEntity];
message.timestamp = [NSDate date];
message.text = self.messageTextField.text;

Magical Record adds Categories to many of the Core Data classes, which adds functionality such as entity creation helpers directly to your NSManagedObject subclass.

Now to move on to the Main View Controller containing the table view. If you have used Core Data before with a table view you have likely used an NSFetchedResultsController to fetch your data. With Magical Record all that setup code can be condensed into one line.

self.fetchedResultsController = [Message MR_fetchAllSortedBy:@"timestamp" ascending:NO withPredicate:nil groupBy:nil delegate:self];

Notice again that this is coming from the NSManagedObject subclass. Again, Magical Record added a convenience method to return an NSFetchedResultsController. There are actually a few versions of this method with varying method signatures, but we chose this one so that the delegate and the sort could be set at once. There are also methods that will allow you to create your NSFetchedResultsController from scratch like you may have previously done, but perform the fetch using Magical Record.

Last but not least, the table view delegate and data source methods will look very familiar to Core Data users. I left the NSFetchedResultsControllerDelegate methods out of this post as they are no different than the examples from Apple, but it is in the demo app code. Once again, notice that Magical Record added a helper to the NSManagedObject subclass for deletion in commitEditingStyle:forRowAtIndexPath:. This saves the developer from having to keep a reference to the NSManagedObjectContext or pulling it from the object itself for deletion.

#pragma mark - UITableViewDataSource methods
 
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
 id sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
 return [sectionInfo numberOfObjects];
}
 
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
 static NSString *CellIdentifier = @"Message Cell";
 UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 Message *message = [self.fetchedResultsController objectAtIndexPath:indexPath];
 cell.textLabel.text = message.text;
 cell.detailTextLabel.text = [self.dateFormatter stringFromDate:message.timestamp];
 return cell;
}
 
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
 if (editingStyle == UITableViewCellEditingStyleDelete) {
 // Delete the row from the data source
 Message *message = [self.fetchedResultsController objectAtIndexPath:indexPath];
 [message MR_deleteEntity];
 }
}

Conclusion

If you have used Core Data before you know there is a lot of work to be done with the Core Data Stack setup, even when making an app this small. Magical Record can cut that time in half if not more. I urge you to look through the demo app and Magical Record to learn all the ways it can save you time.

NOTE: Magical Record takes advantage of Write-Ahead Logging(WAL), an SQLite feature available in SQLite version 3.7+, which correlates to iOS5+. WAL provides some great features with speed increases, more concurrency allowing concurrent read and writes. One disadvantage of WAL is the format of the .sqlite file, it is segmented into three separate files and cannot be opened by tools such as SQLite Data Browser. In this demo application I disable this feature purposely so I can open the database file that is created, and also I prefer the Core Data store to be one file. To make this change I simply commented out the SQLite option that is passed when using the Auto Migrating Store Option in NSPersistentStoreCoordinator+MagicalRecord:MR_autoMigrationOptions. The line is:

[sqliteOptions setObject:@"WAL" forKey:@"journal_mode"];

WAL is only enabled when using the Auto Migrating Store Option so if you are not using the option you will not have to worry about this; also if you don't need to open your database to pre-package it, you can ignore this. More info can be found at http://www.sqlite.org/draft/wal.html.

The demo app can be found here, and is compatible with iOS 5+.