Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

ace2600

macrumors member
Original poster
Mar 16, 2008
71
0
Austin, Texas
Hi,

I'm creating an SQLite file in a Mac app using Core Data and then placing it in my iPhone app. The SQLite file is read-only, it's just mappings for states and cities. I added more cities to the file and am trying to get my iPhone app to read in the new SQLite file and use that as its peristent store.

I was hoping just replacing the SQLite file in the app would work, but it does not.

The steps I've tried are: I run the app in simulator with the old sqlite file. Exit. Replace the sqlite file with the new one in xcode. Add NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to the addPeristentStore options. Then run the app again, but the app only shows the same data from the original file.

I've also tried versioning the model as per the Apple docs and renaming the sqlite file. But still no luck (I'm guessing because the model is still the same?).

Any ideas on how I can have the app read the newer sqlite file?

My iPhone app code:
PHP:
- (NSManagedObjectModel *)geographyObjectModel
{
    if (geographyObjectModel_ == nil)
    {
        NSString *modelPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Geography" ofType:@"mom"];
        NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
        [self setGeographyObjectModel:managedObjectModel];
        [managedObjectModel release];
    }
    
    return geographyObjectModel_;
}

//Geographical data is stored in its own persistent storage
- (NSPersistentStoreCoordinator *)geographyStoreCoordinator
{
    if (geographyStoreCoordinator_ == nil) 
    {    
        NSString *storePath = [[AppDelegate applicationDocumentsDirectory] stringByAppendingPathComponent:@"Geography.sqlite"];
        
        // Check to see if the store already exists
        NSFileManager *fileManager = [NSFileManager defaultManager];
        // Copy the default store if necessary
        if (![fileManager fileExistsAtPath:storePath]) 
        {
            NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Geography" ofType:@"sqlite"];
            if (defaultStorePath) 
            {
                [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
            }
        }
        
        NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self geographyObjectModel]];
        [self setGeographyStoreCoordinator:persistentStoreCoordinator];
        [persistentStoreCoordinator release];
        
        NSMutableDictionary *options = nil;
        //Implement options when updating the model. Then comment options back out.
        options = [NSMutableDictionary dictionary];
        //Uncomment the line below to ignore version hash checks
        //[options setObject:[NSNumber numberWithBool:YES] forKey:NSIgnorePersistentStoreVersioningOption];
        //When migrating, uncomment the setObject lines belows
        [options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
        [options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];        
        NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
        NSError *error;
        if (![[self geographyStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
        {
            // Handle the error.
            DebugLog(@"error: %@", error);
        }
    }
    
    return geographyStoreCoordinator_;
}

If interested, here is the GitHub code for exporting the state - city mappings in Core Data: http://github.com/baalexander/Geography-Importer.
 
I cleaned all targets and deleted the build folder. I did not delete the app from the simulator because I'm trying to test updating.
 
OK, if you look inside your app in the build folder what do you find? It's not possible to open a file that isn't present on the device. Also, you should check the error returned from copyItemAtPath:topath:
 
Thank you for the continued help PhoneyDeveloper.

Your point about copyItemAtPath:topath: forced me to reread that code. It seems obvious now that replacing my old Geography.sqlite file with one of the exact same name won't work as the condition "if (![fileManager fileExistsAtPath:storePath]) " won't occur.

I got it to work by adding the SQLite file as Geography2.sqlite and adding the parameter NSIgnorePersistentStoreVersioningOption (working code at the end).

I hope someone can still explain why NSIgnorePersistentStoreVersioningOption is needed. My guess is because the version/hash values are different because of the data, even though the model is the exact same.

Also, I am quite curious if there is a better way to go about the task of updating the read-only SQLite file than my steps above. I tried versioning the model instead of using this NSIgnorePersistentStoreVersioningOption parameter by clicking on the xcdatemodel file then Design -> Add Model Version then Design -> Set Current Version, but no luck. Also, NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption never helped for this issue.

New working code:
PHP:
- (NSManagedObjectModel *)geographyObjectModel
{
    if (geographyObjectModel_ == nil)
    {
        NSString *modelPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Geography" ofType:@"mom"];
        NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
        [self setGeographyObjectModel:managedObjectModel];
        [managedObjectModel release];
    }
    
    return geographyObjectModel_;
}

//Geographical data is stored in its own persistent storage
- (NSPersistentStoreCoordinator *)geographyStoreCoordinator
{
    if (geographyStoreCoordinator_ == nil) 
    {    
        NSString *storePath = [[AppDelegate applicationDocumentsDirectory] stringByAppendingPathComponent:@"Geography2.sqlite"];
        
        // Check to see if the store already exists
        NSFileManager *fileManager = [NSFileManager defaultManager];
        // Copy the default store if necessary
        if (![fileManager fileExistsAtPath:storePath]) 
        {
            NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Geography2" ofType:@"sqlite"];
            if (defaultStorePath) 
            {
                [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
            }
        }
        
        NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self geographyObjectModel]];
        [self setGeographyStoreCoordinator:persistentStoreCoordinator];
        [persistentStoreCoordinator release];
        
        NSMutableDictionary *options = nil;
        //Implement options when updating the model. Then comment options back out.
        options = [NSMutableDictionary dictionary];
        //Uncomment the line below to ignore version hash checks
        [options setObject:[NSNumber numberWithBool:YES] forKey:NSIgnorePersistentStoreVersioningOption];
        //When migrating, uncomment the setObject lines belows
        //[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
        //[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];        
        NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
        NSError *error;
        if (![[self geographyStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
        {
            // Handle the error.
            DebugLog(@"error: %@", error);
        }
    }
    
    return geographyStoreCoordinator_;
}
 
I'm not entirely clear on your question PhoneyDeveloper.

If you're asking why I use the code below for my object model instantiation:
PHP:
- (NSManagedObjectModel *)geographyObjectModel
{
    if (geographyObjectModel_ == nil)
    {
        NSString *modelPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Geography" ofType:@"mom"];
        NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
        [self setGeographyObjectModel:managedObjectModel];
        [managedObjectModel release];
    }
    
    return geographyObjectModel_;
}
instead of:
PHP:
- (NSManagedObjectModel *)geographyObjectModel
{
    if (geographyObjectModel_ == nil)
    {
        [self setGeographyObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
    }

    return geographyObjectModel_;
}
it's because I have multiple models and found the first way easier to deal with them. If you meant something different, could you explain further?
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.