Frequently, it’s useful to ship an iOS app with a preconfigured CoreData database, so that on initial launch the data doesn’t need to be retrieved from a webservice before presenting it to the user / or immediately loaded from a plist shipping with the file.
This used to be easy pre iOS 7.0: Launch in the simulator and ensure you’ve populated the datastore with the data you want to chip. Then find the sqlite3 datafile on your mac. That’s easily done with a breakpoint in the persistentStoreCorrdinator:
Then copy it into your project
BINGOSABI:bingosabi$ cd /Users/bingosabi/Library/Developer/CoreSimulator/Devices/ 7FD9730C-F002-414D-9F72-8D6BAA750A3B/data/Containers/Data/Application/0A3AB0EE- D557-4FE7-9683-3C337C64A7E9/Documents/db.sqlite BINGOSABI:Documents$ cp db.sqlite ~/Projects/ExampleApp/Resources
In iOS 7.0 however, but Apple updated the shipping version of sqlite3 which enabled a separate journaling file. Now a database has three files.
BINGOSABI:Documents bingosabi$ ls db.sqlite db.sqlite-shm db.sqlite-wal
So in order to ship a populated databases we have to do a few extra steps.
sqlite> pragma journal_mode=off; off sqlite> vacuum sqlite> .exit
Copy ONLYthe vacuumed db.sqlite to the Xcode project and include it.
BINGOSABI:bingosabi$ cd /Users/bingosabi/Library/Developer/CoreSimulator/Devices/ 7FD9730C-F002-414D-9F72-8D6BAA750A3B/data/Containers/Data/Application/0A3AB0EE- D557-4FE7-9683-3C337C64A7E9/Documents/db.sqlite BINGOSABI:Documents$ cp db.sqlite ~/Projects/ExampleApp/Resources
If you used the default template for a Core Data app, then replace the default persistentStoreCorrdinator with the following code: (Note that you have to always touch that anyways if you’re submitting to the store due to the abort(); poison pill which Apple has in the template. Not removing the abort(); is a rejection-able offense.
So replace this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort(); //POISON PILL
}
return _persistentStoreCoordinator;
}
with this:
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
//This is required for cached data lest Apple reject for immediately writing data to Documents which isn't user data and has default iCloud backup attribute.
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}
return success;
}
- (void) copyDatabaseFromBundle:(NSURL *)storeURL {
NSError *error = nil;
NSString *databaseName = [[storeURL lastPathComponent] stringByDeletingPathExtension];
NSString *databaseExtension = [storeURL pathExtension];
NSURL *shippingDatabaseImage = [[NSBundle mainBundle] URLForResource:databaseName withExtension:databaseExtension];
NSURL * toURL = [[[self applicationDocumentsDirectory] URLByAppendingPathComponent:databaseName] URLByAppendingPathExtension:databaseExtension];
[[NSFileManager defaultManager] copyItemAtURL:shippingDatabaseImage toURL:toURL error:&error];
[self addSkipBackupAttributeToItemAtURL:toURL];
}
- (void) deleteDatabase:(NSURL *)storeURL {
//Delete all the write ahead log files.
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
[[NSFileManager defaultManager] removeItemAtURL:[NSURL URLWithString:[storeURL.absoluteString stringByAppendingString:@"-wal"]] error:nil];
[[NSFileManager defaultManager] removeItemAtURL:[NSURL URLWithString:[storeURL.absoluteString stringByAppendingString:@"-shm"]] error:nil];
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (!_persistentStoreCoordinator) {
NSManagedObjectModel *model = [self managedObjectModel];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if ([storeURL checkResourceIsReachableAndReturnError:&error] == NO) {
[self copyDatabaseFromBundle:storeURL];
}
//Handle lightweight version migration
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
//Just Start Over.
[self deleteDatabase:storeURL];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
}
});
}
return _persistentStoreCoordinator;
}
C’est tout!
