Core Data on iOS 5 Tutorial: How To Preload and Import Existing Data

Adam Burkepile Adam Burkepile
Failed Banks Preloaded Data with Core Data

Failed Banks Preloaded Data with Core Data

Note from Ray: iOS Tutorial Team member Adam Burkepile has kindly updated the Core Data series to iOS 5 – I’ll post an announcement about it in a bit.

This tutorial was completely rewritten in order to show a more elegant way of preloading data by making a Mac command line app that reuses your iPhone app’s Core Data model.

I wanted to repost this specially so everyone is aware of the update/rewrite. Enjoy! :]

This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter.

This is the second part of a three-part series to help get you up to speed with the basics of Core Data quickly.

In the first part of the series, we created a visual data model for our objects, ran a quick and dirty test to make sure it works, and hooked it up to a table view so we could see a list of our objects.

In this part of the series, we’re going to discuss how to import or preload existing data into Core Data so that we have some good default data when our app starts up.

Note that there was an earlier version of this tutorial that showed you a hackish way to preload data by directly manipulating the SQLite database. The method shown in this tutorial is much preferred as it’s more elegant and less likely to break as things change with Core Data in the future.

In the final part of the series, we’re going to discuss how we can optimize our app by using NSFetchedResultsController, to reduce memory overhead and improve response time.

Before we begin, make sure you have the project where we left it off in part 1. If you need a fresh copy, you can download it here.

Preloading / Importing Existing Data

So how do we preload data into a Core Data store, anyway? Well, there are two popular solutions to this problem:

  1. Fill in Core Data on startup from external source. For this the app can start up, notice that the database hasn’t been imported yet, and start reading in data from an external source (such as an SQLite database or XML file) and then start inserting the data into Core Data.
  2. Provide pre-filled in SQLite database. For this we’ll let Core Data create the database structure for us based on the model, and then we populate the database with a utility app. The utility app could be a Mac or iPhone app that uses Core Data to populate the database via Core Data APIs, or some kind of program that fills in the SQLite database directly. Once the database is populated, just include it with the app and make the app use it as the default database if no database already exists.

In this tutorial we are going to go with option #2, and show you how you can make a simple utility app to create a preloaded Core Data database you can use in your app.

Getting Started

The basis for the method we are going to use is that Core Data on iOS is the same Core Data on OS X, and they can both use the same models and classes.

This means we can write a simple console application on OS X to import data from a data source, stick it into a Core Data data store, and move that data store over to our iOS project. Isn’t that great?!

Let’s try this out and make a Mac command line app to preload our data. Open up Xcode and create a new project, and choose the Mac OSX\Application\Command Line Tool template.

Enter “CoreDataTutorial2” as the name fo the project, change the type to “Core Data” and make sure “Use Automatic Reference Counting” is on.

Finish creating the project, then select “CoreDataTutorial2.xcdatamodeld” and delete it. Select “Move to Trash” when it asks.

Next go into the directory for tutorial 1 and find the following files:

  • FailedBankCD.xcdatamodeld
  • FailedBankInfo.h
  • FailedBankInfo.m
  • FailedBankDetails.h
  • FailedBankDetails.m

Copy those files into the directory for this project and then drag them into Xcode.

Make sure the “Copy items into destination group’s folder (if needed)” is NOT checked but be sure to check the “Add to targets” checkbox for “CoreDataTutorial2”.

Select the main.m. You’ll notice that because we selected this as a Core Data type application, we get the same boilerplate Core Data methods we saw in tutorial 1. What we’re gonna do now is make some changes to use our model classes from the iOS project and generate the Core Data entities.

In the managedObjectModel() method replace

NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];
path = [path stringByDeletingPathExtension];

with

NSString *path = @"FailedBankCD";

This is pointing the application to the FailedBankCD.xdatamodeld we added instead of the CoreDataTutorial2.xdatamodeld we deleted in the beginning.

Compile and run, and verify that you have no errors.

If you built and ran it previously, you will get the model-mismatch error we discussed in tutorial 1. To delete the sqlite db, hold option(alt) and go to the menu and select “Products” and “Clean Build Folder…”.

If you see an error like this:

NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator 
with a nil model'

This is because the code is looking for a ‘momd’ file (the file for a versioned Core Data Model), but if your model isn’t versioned it will be saved as plain old ‘mom’. As this article explains, the quickest fix is to replace the line in managedObjectModel() like so:

 NSURL *modelURL = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"mom"]];

Now it should compile and run fine (with no output yet).

Importing the Data

Now on to the fun part – preloading our data into a Core Data database!

For our example, we’ll be importing data from a JSON file. In your apps you may want to import data from a different format, but the core idea shown here (using a Mac app to read a file and load it into a Core Data db) will still apply.

Let’s try it out! Right click on the project and select “New File”. Select “Other” category and “Empty”.

Name the file “Banks.json” and make sure to include the file in the “CoreDataTutorial2” target.

Paste the following into the new file:

[{ "name": "Bank1", "city": "City1", "state": "State1", "zip": 11111, "closeDate": "1/1/11" },
 { "name": "Bank2", "city": "City2", "state": "State2", "zip": 22222, "closeDate": "2/2/12" },
 { "name": "Bank3", "city": "City3", "state": "State3", "zip": 33333, "closeDate": "3/3/13" },
 { "name": "Bank4", "city": "City4", "state": "State4", "zip": 44444, "closeDate": "4/4/14" } ]

This is a JSON encoded string containing 4 dictionaries in an array. Each dictionary has a couple properties that correspond to the properties we have in our FailedBankInfo/FailedBankDetails objects.

If you’re a little rusty on how JSON works, check out this tutorial.

Next, we need to tell the application to copy this file into the products dictory when it builds. Select the project file, then the “CoreDataTutorial2” target. Select the “Build Phases” tab, click “Add Build Phase”, and select “Add Copy Files”. Change the destintion to “Products Directory”. Lastly, drag the “Banks.json” file into the “Add files” section.

At this point we have an application that starts up, initializes a Core Data store using our FailedBank model and classes and has a data source with the Banks.json file. Now we are going to:

  • Load the JSON file
  • Deserialize the JSON file into an Objective C array
  • Loop through the array, creating a managed object for each item
  • Save them into Core Data!

Let’s get coding! Open main.m and add the following code to the main function (at the end of the autoreleasepool block):

    NSError* err = nil;
    NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
    NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
                                                     options:kNilOptions
                                                       error:&err];
    NSLog(@"Imported Banks: %@", Banks);

So your main function should look like this now:

int main(int argc, const char * argv[])
{
 
    @autoreleasepool {
        // Create the managed object context
        NSManagedObjectContext *context = managedObjectContext();
 
        // 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);
        }
 
        NSError* err = nil;
        NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
        NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
                                                         options:kNilOptions
                                                           error:&err];
        NSLog(@"Imported Banks: %@", Banks);
 
    }
    return 0;
}

This uses the new built-in NSJSONSerialization API to easily convert a JSON document into core Foundation types like NSArray, NSDictionary, etc. To learn more, check out this tutorial.

Give the application a run. You should see this print out:

2012-04-14 22:01:34.995 CoreDataTutorial2[18388:403] Imported Banks: (
        {
        city = City1;
        closeDate = "1/1/11";
        name = Bank1;
        state = State1;
        zip = 11111;
    },
        {
        city = City2;
        closeDate = "2/2/12";
        name = Bank2;
        state = State2;
        zip = 22222;
    },
        {
        city = City3;
        closeDate = "3/3/13";
        name = Bank3;
        state = State3;
        zip = 33333;
    },
        {
        city = City4;
        closeDate = "4/4/14";
        name = Bank4;
        state = State4;
        zip = 44444;
    }
)

So we can see we now have the data in Objective-C objects that we can easily manipulate. Now we can easily import these objects into CoreData just like we did at the end of tutorial 1.

First, add some imports you need to the top of the file:

#import "FailedBankInfo.h" 
#import "FailedBankDetails.h"

Then add the following below the code you just added earlier in main:

[Banks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    FailedBankInfo *failedBankInfo = [NSEntityDescription
                                      insertNewObjectForEntityForName:@"FailedBankInfo"
                                      inManagedObjectContext:context];
    failedBankInfo.name = [obj objectForKey:@"name"];
    failedBankInfo.city = [obj objectForKey:@"city"];
    failedBankInfo.state = [obj objectForKey:@"state"];
    FailedBankDetails *failedBankDetails = [NSEntityDescription
                                            insertNewObjectForEntityForName:@"FailedBankDetails"
                                            inManagedObjectContext:context];
    failedBankDetails.closeDate = [NSDate dateWithString:[obj objectForKey:@"closeDate"]];
    failedBankDetails.updateDate = [NSDate date];
    failedBankDetails.zip = [obj objectForKey:@"zip"];
    failedBankDetails.info = failedBankInfo;
    failedBankInfo.details = failedBankDetails;
    NSError *error;
    if (![context save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }
}];
 
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo"
                                          inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (FailedBankInfo *info in fetchedObjects) {
    NSLog(@"Name: %@", info.name);
    FailedBankDetails *details = info.details;
    NSLog(@"Zip: %@", details.zip);
}

This is essentially the same code we used in tutorial 1 except for the fact we are using the enumerateObjectsUsingBlock: method to loop through the array of banks and insert one bank and save the context. Then we issue a fetch request and list all the banks.

Give it a run. It should now output the array you saw earier along with this:

2012-04-14 22:15:44.149 CoreDataTutorial2[18484:403] Name: Bank1
2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Zip: 11111
2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Name: Bank2
2012-04-14 22:15:44.151 CoreDataTutorial2[18484:403] Zip: 22222
2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Name: Bank3
2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Zip: 33333
2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Name: Bank4
2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Zip: 44444

Ta-da! That’s your data in Core Data. The exciting part is you can totally customize the way that data comes in. Instead of the small static json file we used, you could use a much larger/a couple json files, a xml file, export a spreadsheet file as a csv file and use that, or even pipe in web service! The possibilities really are endless.

“Doctor, you’re needed in Xcode”

Now we do brain surgery. We need to take the sqlite database we just generated in you OS X console applicaiton and transplant it into the iPhone app. The easiest way to find the sqlite db is the right click on the CoreDataTutorial2 product and select “Show in Finder”.

 

This will open a new Finder window to location that the project got built. Here, you can see four files:

  • Banks.json – The json file with the bank information in it (remember we added the copy file build phase)
  • CoreDataTutorial2 – This is the application
  • FailedBankCD.momd (or just .mom) – this is the compiled version of the Managed Object Model (FailedBankCD.xdatamodeld)
  • CoreDataTutorial2.sqlite – the sqlite db that is generated by the application and where Core Data stores its infomration according to the model that FailedBankCD.momd specifies. If you have a viewer like this one you can even open it and view the raw data.

That “CoreDataTutorial2.sqlite” file is the juicey bit we need. Copy that file into the directory for the project from tutorial 1 then open the tutorial 1 project in Xcode.

Drag the “CoreDataTutorial2.sqlite” file from the Finder into the Xcode project (again make sure that “Copy items into destination group’s folder (if needed)” is NOT checked but “Add to targets” is checked for “FailedBanksCD”).

Lastly open up “FBCDAppDelegate.m”. Scroll down to the persistentStoreCoordinator method. Right under the NSURL *storeURL = [[self app... line, add:

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

This chunk of code checks to see if a sqlite db already exists for this app. If it doesn’t exist, it finds the path for the preloaded sqlite db we loaded and tries to copy the db to the path for the normal app db. IT’S THAT SIMPLE! Give it a run. You should see something like this:

 

We see our four banks and then we see that extra bank than get added from the code we wrote in tutorial 1. If you don’t see this, it’s probably because a sqlite db was already there so it’s not overwriting it. Just delete it like we did in tutorial 1.

Where to Go From Here?

You can download the code for the project here (direct download).

At this point, we have a detail view that works pretty much as efficiently as the way it did in our SQLite example. However, with Core Data, with just a couple more steps we can make it even more efficient. So in the next tutorial we cover how to do that by using NSFetchedResultsController!

If you have any questions about this tutorial or Core Data in general, please join the forum discussion below!


This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter.

Adam Burkepile
Adam Burkepile

Adam Burkepile is currently a full-time Software Consultant and independent iOS developer. If he isn’t at the computer, he’s probably getting punched in the face at Krav Maga. Check out his latest app – Pocket No Agenda. You can reach him for work or just to chat at Twitter, Github, his website, or email.

User Comments

109 Comments

[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
  • Hi,

    I don't understand the purpose to register the json file in the Products directory, as explained here :

    Next, we need to tell the application to copy this file into the products dictory when it builds. Select the project file, then the CoreDataTutorial2 target. Select the Build Phases tab, click Add Build Phase, and select Add Copy Files. Change the destintion to Products Directory. Lastly, drag the Banks.json file into the Add files section.


    I don't really know what the Products directory stands for.
    (Nor what a "target" really means in xcode)

    Thanks
    PaulWS
  • The reason to register the json file is so that it gets copied to your device (or simulator) along with the application.
    Richard Caseyrcasey
  • Thanks rcasey,
    (sorry about the delay for my response) So what is the difference if you let the json along with the other files such as .h and .m files, or if you register as it is described in the tutorial? When we create a document and put it next to the .h and .m files, it cannot be overwritten later on?

    Thanks again
    PaulWS
  • If you don't include the json file in the target, then it won't get copied to your device and the app will be unable to find it.

    The .h and .m files are compiled to create the app itself. Any supporting files like images, text files, etc need to be included in the target so they will be copied to the device as part of the application bundle. That makes them available for the app.

    Hope that helps!
    Richard Caseyrcasey
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Gaming!

    Loading ... Loading ...

Last week's winner: Core Data Sync.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in January: WatchKit.

Sign Up - January

Our Books

Our Team

Tutorial Team

... 58 total!

Update Team

  • Riccardo D'Antoni
  • Zouhair Mahieddine

... 14 total!

Editorial Team

  • Matt Galloway

... 18 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!