The Cookbook

Core Data: Automate master data preloading

Problem

Given a generic iOS application ‘Blog’ with data model as below:

We want to prepopulate data for ‘Categories’ table in our SQLite database.

Solution

This solution uses the approach described in Core Data on iOS 5 Tutorial: How To Preload and Import Existing Data, we will create an OS X Command Line Tool application, which will reference the data model in our ‘Blog’ application and prepopulate the data. Except we automate the building process.

Step 1

Make sure you are using Xcode Workspace with your project and skip to Step 2. If not, follow the steps below to create a new workspace:

  1. Open your project in Xcode
  2. Goto File > New > Workspace… or use the shortcut Command + Control + N
  3. Name the new workspace the same as your project and save it in your project folder.
  4. Quit Xcode
  5. Open the Workspace $ open Blog.xcworkspace
  6. Drag your project from Finder to the Workspace
  7. We have successfully added your project into a workspace.

Step 2

  1. While your workspace is open create a new project by selecting File > New > Project
  2. From the prompt window select OS X > Application > Command Line Tool and click Next
  3. In the next window enter MasterDataLoader for the Product Name, select Core Data in the Type pulldown and click Next
  4. In the next window select the folder where your workspace is stored, select your workspace for Add to and Group pulldowns. Click Create.
  5. Now you have two applications in your workspace
  6. Remove the MasterDataLoader.xcdatamodeld file from the MasterDataLoader application and Move to Trash
  7. Open the main.m file in the MasterDataLoader application
  8. Change the lines in managedObjectModel() and managedObjectContext() methods from
1
2
NSString *path = @"MasterDataLoader";
path = [path stringByDeletingPathExtension];
1
2
NSString *path = [[NSProcessInfo processInfo] arguments][0];
path = [path stringByDeletingPathExtension];

to

1
NSString *path = @"Blog";

Now run MasterDataLoader application.


You should get an error Cannot create an NSPersistentStoreCoordinator with a nil model. Now we made sure our command line app runs and can’t find a compiled data model object. Great!

Step 3

Add a new Run Script build phase to your ‘Blog’ application, name it Run MasterDataLoader and drag it under Compile Sources phase.

Now paste this code in the run script you just created and save.

1
2
3
4
5
6
7
8
echo "Copying momd to MasterDataLoader folder"
cp -R ${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app/${TARGET_NAME}.momd ${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}/
cd ${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}/
echo "Running MasterDataLoader"
./MasterDataLoader
echo "Moving ${TARGET_NAME}.sqlite"
mv ./${TARGET_NAME}.sqlite ${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app/
echo "MasterDataLoder finished."

Edit the scheme for ‘Blog’ application. Add MasterDataLoader in build targets.

This step makes sure that MasterDataLoader is build before the ‘Blog’ app.

Step 4

Open AppDelegate.m in the ‘Blog’ application and add the following code

1
2
3
4
5
6
7
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
  NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Blog" ofType:@"sqlite"]];
  NSError* err = nil;
  if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
    NSLog(@"Oops, could copy preloaded data");
  }
}

under the line

1
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Blog.sqlite"];

As you can see, here we check if the SQLite database already exists. If not, we copy the one which is in the main bundle to the ‘Documents’ folder.

Right now our SQLite datamase is empty. Let’s fetch ‘Categories’ and check if the table is empty. Add the following code to the application:didFinishLaunchingWithOptions: method just before return YES line.

1
2
3
4
5
6
7
8
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSError *error =  nil;
NSArray *cats = [self.managedObjectContext executeFetchRequest:request error:&error];
for (Category *cat in cats) {
  NSLog(@"cat: %@", cat.name);
}

Don’t forget to import Category.h

1
#import "Category.h"

Run the ‘Blog’ application. The console should print nothing.

Step 5

Lets add some Categories. Drag the Category.h file from ‘Blog’ application to MasterDataLoader application.

Open the main.m file. Import Category.h.

1
#import "Category.h"

Replace the code in main() method

1
2
3
4
5
6
7
// Custom code here...
// Save the managed object context
NSError *error = nil;
if (![context save:&error]) {
    NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
    exit(1);
}

with

1
2
3
4
5
6
7
8
9
NSArray *categories = @[@"objective-c", @"ruby", @"python"];
for (NSString *name in categories) {
    Category *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:managedObjectContext()];
    cat.name = name;
    if (![context save:&error]) {
        NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
        exit(1);
    }
}

Before running

Delete ‘Blog’ application from the simulator.

Run

Run the ‘Blog’ application.


The console should print three categories:

1
2
3
2013-05-07 01:00:13.911 Blog[61827:c07] cat: objective-c
2013-05-07 01:00:13.913 Blog[61827:c07] cat: ruby
2013-05-07 01:00:13.913 Blog[61827:c07] cat: python

Discussion

This way you can reference any managed object from MasterDataLoader app and preload the data.

Comments