Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preloading a database in iOS 7

In the past I have released my app with a database that was preloaded, so the user didn't have to update it on the first run. There was some code I found in another question on SO (sorry, don't have the link anymore) that I added to my App Delegate's persistentStoreCoordinator method:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db.sqlite"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]])
    {
        NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"db" ofType:@"sqlite"]];
        NSError* err = nil;

        if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err])
        {
            NSLog (@"Error - Could not preload database.");
        }
    }

//... more code generated from the template here
}

When I try to do this in iOS 7, I don't get any errors, but the database is empty (even though the database in my mainBundle has all the info I'm expecting). I noticed that there are more database files (a .sqlite-shm file and a .sqlite-wal file) in the applicationDocumentsDirectory. Do I need to do something with those files as well? Or is it no longer possible to have a preloaded database ship with the app?

EDIT: I tried adding code to copy the new .sqlite-shm and .sqlite-wal files as well, but that doesn't help.

like image 834
GeneralMike Avatar asked Nov 14 '13 16:11

GeneralMike


2 Answers

I copied the .sqlite-shm and .sqlite-wal files as well and it worked:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent: @"emm_samples.sqlite"];

// Check if the sqlite store exists
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
    NSLog(@"File not found... copy from bundle");

    // copy the sqlite files to the store location.
    NSString *bundleStore = [[NSBundle mainBundle] pathForResource:@"emm_samples" ofType:@"sqlite"];
    [[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];

    bundleStore = [[NSBundle mainBundle] pathForResource:@"emm_samples" ofType:@"sqlite-wal"];
    storePath = [documentsDirectory stringByAppendingPathComponent: @"emm_samples.sqlite-wal"];
    [[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];

    bundleStore = [[NSBundle mainBundle] pathForResource:@"emm_samples" ofType:@"sqlite-shm"];
    storePath = [documentsDirectory stringByAppendingPathComponent: @"emm_samples.sqlite-shm"];
    [[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];
}
else {
    NSLog(@"File exists");
}

(Based on: Core Data Store included in App Bundle)

like image 50
dawid Avatar answered Oct 16 '22 23:10

dawid


Core Data has changed a bit in iOS 7, mainly how saves are performed.

Write Ahead Logging (wal) was introduced to improve performance, hence the WAL sqlite file you see.

You can tell your app to use the old 'journal mode':

You can specify the journal mode by adding the NSSQLitePragmasOption to the options when calling addPersistentStoreWithType:configuration:url:options:error. E.g. to set the previous default mode of DELETE:

NSDictionary *options = @{ NSSQLitePragmasOption : @{@"journal_mode" : @"DELETE"} };

Source

I am not sure if this will fix your issue, but it is the big thing that has changed with Core Data in iOS 7

If you want to learn more about WAL, I suggest watching WWDC session #207, "Whats New In Core Data & iCloud"

like image 37
RyanG Avatar answered Oct 17 '22 00:10

RyanG