views:

2390

answers:

4

In a previous project, I built an iPhone app for 2.2.x that used SQLite. It had existing data (in XML form) that needed to be pre-loaded into the build. So I wrote a small tool which used libxml2 to parse the XML, and then write out an SQLite database, which then was included directly in the build as a resource. This worked out great.

I will be starting a new project for a different client in a few weeks that has mostly the same parameters. I will have some existing data I will need to parse and dump into a file that the app will display. However, this time around I'd like to use Core Data and build the app for 3.x devices. However, I don't have explicit and direct access to the underlying database schema that Core Data uses. (Which is kind of the point of Core Data)

How can I pre-load existing data into a Core Data-based iPhone app? Can I automate the process (similar to what I did above with SQLite)?

+2  A: 

I don't believe Core Data provides this type of functionality inherently. If I was in your shoes, I'd write a small tool that did the XML parsing and did whatever it needed to create the Core Data model from that data and persist it. Then, just move the sqlite database Core Data generates to your real project.

Then perhaps write some code that copies that default database into the proper location on the iPhone if a database doesn't already exist there. That will also allow you to easily revert to the "clean" data if you get yourself into a bad state since you can just delete the database file and re-run the app.

Marc W
+1  A: 

Assuming you're using CoreData with an SQLite backing, then absolutely. The most direct option would be to let the application generate the schema for you, then using that empty/shell DB, run your xml-to-sql migration tool. You would just need to update your tool to account for the structure that CoreData generates for you from the xcdm.

Alternatively, you could use your tool to write an sqlite db that is included in your bundle, then at runtime on startup, read in the data from the sqlite db, spit it into CoreData, and carry on. This would be using your sqlite DB as the container for the "preload data", and all further data interaction within CoreData's data store.

It's probably a matter of preference.

Ken Mason
By application, do you mean let Xcode generate the schema? Or does the schema only get built at run-time?
Shaggy Frog
I'm not 100% sure. I want to say that it's generated at run-time (I believe part of CoreData init is to bring a data store up to date if needed), but you'd need to double check on that.
Ken Mason
Xcode will not generate the schema. It's a runtime operation.
Marc W
+1  A: 

You can follow a similar approach. The easiest way is likely to setup your core data managed context and then read in your XML file, creating the managed objects as you parse the XML file.

    NSManagedObjet *managedObject = [[NSEntityDescription insertNewObjectForEntityForName:@"DataTypeName"
                                                                   inManagedObjectContext:managedContext] retain];

    [managedObject setValue:@"some data" forKey:@"keyName"];
    /* ... */
m5h
This is a valid solution. However, I'd prefer to do this kind of parsing "offline", so the app only ever sees a SQLite DB file.
Shaggy Frog
+2  A: 

I'm a little late to this party, but i'm doing something similar for GroceryList. I have data stored in plist files that i need in my CoreData sqlite store. I wrote a command line foundation tool that runs on my Mac which parses the plist files and then using my Core Data object model creates a sqlite store. I run this command line tool as part of my build (for some build configurations) so that i can regenerate the data at will. To create this kind of tool in xcode, select File -> New Project -> Mac OS X -> Command Line Tool and select "Core Data" from the "type" menu. Here's some sample code:

#import <objc/objc-auto.h>

int main (int argc, const char * argv[]) {

    objc_startCollectorThread();

    //You may not know this, but NSUserDefaults can be used to parse command line arguments!
    //in this case, the arguments are passed in like this:
    // -fullMomPath /hd/some/path/file.mom -fullStorePath /hd/some/path/file.sql
    //by passing in the storePath, the calling script knows where the sqlite file will be and can copy it to the resulting application bundle
    NSUserDefaults *args = [NSUserDefaults standardUserDefaults];   
    NSString *momPath = [args stringForKey:@"fullMomPath"];
    NSString *storePath = [args stringForKey:@"fullStorePath"];

    // Create the managed object context
    NSManagedObjectContext *context = managedObjectContext(momPath, storePath);


    //build and save your NSManagedObjects here
    //in my case, i parse some plist files and create GroceryList type stuff, but whatever you do is your business.

    return 0;
}

NSManagedObjectModel *managedObjectModel(NSString* momPath) {

    static NSManagedObjectModel *model = nil;

    if (model != nil) {
        return model;
    }

    NSURL *modelURL = [NSURL fileURLWithPath:momPath];
    model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    return model;
}



NSManagedObjectContext *managedObjectContext(NSString* momPath, NSString* storePath) {

    static NSManagedObjectContext *context = nil;
    if (context != nil) {
        return context;
    }

    context = [[NSManagedObjectContext alloc] init];

    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel(momPath)];
    [context setPersistentStoreCoordinator: coordinator];

    NSString *STORE_TYPE = NSSQLiteStoreType;

    NSURL *url = [NSURL fileURLWithPath:storePath];

    NSError *error;
    NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:nil error:&error];

    if (newStore == nil) {
        NSLog(@"Store Configuration Failure\n%@",
              ([error localizedDescription] != nil) ?
              [error localizedDescription] : @"Unknown Error");
    }

    return context;
}
kalperin