Updating An sqlite CoreData Backing Store On iOS

Something that has unpleasantly surprised many an iOS developer is that to update the data in an sqlite database that is the backing store for CoreData you need to do more than just make a new release of your app with a new database file included in your bundle. The reason for this is that CoreData can’t write to your sqlite database while it is in the bundle, so the database is typically saved to the Documents folder.

NOTE: With the advent of iCloud the Documents folder should only be used for data, (including sqlite databases) that is user generated. For data, (including sqlite databases) that is not user generated you should use the Library/Cache directory instead of the Documents directory. For the purposes of this tutorial I will leave it as saving to the Documents directory but be aware that you may need to save to somewhere else.

To update your database you will need to replace the old one in the Documents folder with the new one from the bundle. I usually do this in two steps, first deleting the old sqlite file from the Documents folder and then writing the new one in its place.

Of course you don’t want this to happen every time the user starts the app. You really only want to do it once when the user has opened a new version of the app that they have downloaded. To handle that I make use of the bundle version from the Info.plist and good old NSUserDefaults (which I do still use for some things).

Here is the code I use in my AppDelegate’s application:didFinishLaunchingWithOptions: method:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSString *bundleVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
    if ([userDefaults objectForKey:kVERSION]) {
        NSString *version = [userDefaults objectForKey:kVERSION];
        if (![version isEqualToString:bundleVersion]) {
            [self replaceDatabase];
            [userDefaults setObject:bundleVersion forKey:kVERSION];
        }
    } else {
        [self replaceDatabase];
        [userDefaults setObject:bundleVersion forKey:kVERSION];
    }

The key “kVERSION” is just a # define of @”version”. All I am doing here is getting the userDefaults and the bundleVersion and then checking if there is a bundleVersion saved in userDefaults. If there is I check if it is different from the current bundleVersion of the app. If it is different then I call my replaceDatabase method which we will get to in a minute. Once that has run I make sure to save the new version to userDefaults to eliminate this code running again while the app is still on this current version. If the version was found to be the same then we exit the if statement and are done. No need to update the database because we should already have the latest.

If there was not a previous bundleVersion saved, maybe because you have added this code to a later release of your app, then we do the same thing as above, replace the database and then save the version to userDefaults.

Pretty straightforward stuff. Now lets take a look at the replaceDatabase method:

- (void)replaceDatabase
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
        // remove old sqlite database from documents directory
    NSURL *dbDocumentsURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Data.sqlite"];
    NSString *dbDocumentsPath = [dbDocumentsURL path];
    if ([fileManager fileExistsAtPath:dbDocumentsPath]) {
        NSError *error = nil;
        [fileManager removeItemAtPath:dbDocumentsPath error:&error];
        if (error) {
            DLog(@"Error deleting sqlite database: %@", [error localizedDescription]);
        }
    }
    
        // move new sqlite database from bundle to documents directory
    NSString *dbBundlePath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"sqlite"];
    if (dbBundlePath) {
        NSError *error = nil;
        [fileManager copyItemAtPath:dbBundlePath toPath:dbDocumentsPath error:&error];
        if (error) {
            DLog(@"Error copying sqlite database: %@", [error localizedDescription]);
        }
    }
}

Here we first get a reference to the default fileManager which we will be using. Next we need to get the path to the database file in the documents directory. To do that we first get the URL to the file using Apple’s convenience method applicationDocumentsDirectory which gives us an NSURL for the Documents directory and call the URLByAppendingPathComponent: method on that. I think that the applicationDocumentsDirectory method is included in your AppDelegate by default but I will also post it below for your reference. Once we have the URL for the database we simply pass the ‘path’ message to the URL and that gives us the path. Now we just check if the file is in fact at that location and if it is we remove it.

So that is the first part done. Now to copy the new database into place. To do that we need to get the path to the new database in the bundle. We do this with the NSBundle pathForResource: method. If the path is good we then do a copy of the database in the bundle to the Documents directory path and we are done.

The next time CoreData accesses the persistent store it will find the sqlite database in the same place as always with the same name but with your new data.

Finally, just in case you can’t find it, here is the applicationDocumentsDirectory method which as you can see is not very complex:

- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

As always hope this is useful info for some of you out there.

Share and Enjoy:
  • printfriendly Updating An sqlite CoreData Backing Store On iOS
  • digg Updating An sqlite CoreData Backing Store On iOS
  • stumbleupon Updating An sqlite CoreData Backing Store On iOS
  • delicious Updating An sqlite CoreData Backing Store On iOS
  • facebook Updating An sqlite CoreData Backing Store On iOS
  • yahoobuzz Updating An sqlite CoreData Backing Store On iOS
  • twitter Updating An sqlite CoreData Backing Store On iOS
  • googlebookmark Updating An sqlite CoreData Backing Store On iOS
  • reddit Updating An sqlite CoreData Backing Store On iOS

About jjob

I'm a technologist and a music producer. I make sound, write code and build things with electronics and microcontrollers.
This entry was posted in app, code, iOS, iPad, iPhone, Objective-C and tagged , , , , , , , . Bookmark the permalink.

5 Responses to Updating An sqlite CoreData Backing Store On iOS

  1. Frank says:

    Hello

    I am a noob to xcode. Do you have a sample app that you can share, I can’t seem to get the code to work correctly.

  2. Stealth says:

    You said: “Something that has unpleasantly surprised many an iOS developer is that to update”. Ha! I am one of them. I am on my second day of google search/ stackoverflow search. No avail. I have an app on the app store with exact scenario. Almost nothing out there deals with this specific scenario and voila! you have a solution. Thanks a lot for the post. You have saved me hours of sleeplessness.

  3. Adri says:

    Same here, another unpleasantly suprised developer, so thank you so much for this post, you saved me lots of hours, and this is not the first time that your posts help me!

  4. Lee says:

    ‘The key “kVERSION” is just a # define of @”version”’

    I am fairly new to Objective C, and I have absolutely no idea what this means. Could you please explain?

    • jjob says:

      That isn’t specifically an Objective-C thing. A #define comes from C and is used in at least all the C related languages. It just lets you define a constant. Anywhere the compiler sees the key for that constant it just replaces it with the value of the constant. In the example here, kVersion is the key, and @”version” is the value.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>