6

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.

GeneralMike
  • 2,951
  • 3
  • 28
  • 56

2 Answers2

6

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)

Community
  • 1
  • 1
dawid
  • 374
  • 5
  • 9
5

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"

Community
  • 1
  • 1
RyanG
  • 4,393
  • 2
  • 39
  • 64
  • Yep, adding that `options` dictionary does the trick. I'll check out the video you linked, and hopefully it's not something I'll end up needing =). Otherwise I guess I'll have to find another solution, but this seems to work for now. – GeneralMike Nov 14 '13 at 16:29
  • WAL does increase performance in multithreaded applications, so be careful about changing the journal mode. I don't know exactly how to ship a pre-populated database with WAL enabled, though. – David Snabel-Caunt Nov 14 '13 at 16:38
  • @DavidCaunt: I'm not too concerned about the performance for my app at this point. Database operations were pretty slow in a previous version of my app, but I significantly trimmed down a lot of my tables to eliminate wasted space and changed my fetches to better get the information I'm going to need in a session. After those modifications, there's virtually no detectable delay. If something changes and performance starts suffering again, I'll keep in mind to look into WAL again though - Thanks. – GeneralMike Nov 14 '13 at 17:02
  • 1
    @DavidCaunt: Followup - the WWDC video mentioned that the WAL style probably won't play well with with `NSFileManager`, which is what I was using to do the preloading, so I guess this is anticipated behavior. Whether Apple is going to be changing anything to make them compatible in the future is probably anyone's guess. If they don't, it looks like we would need to come up with a completely new way of getting the database to install along with the app. – GeneralMike Nov 14 '13 at 20:54