Search code examples
sqlitecore-datakeychainios7.1core-data-migration

Core data update of existing app fails on iOS7.1


I have an app on the AppStore that was submitted in Dec 13 using Xcode 4.3, it has a core data model (version 2). I am now going to release the next version which has updated core data model. When I run the code on debug mode, the migration works fine. But when I do a release via TestFlight the migration fails and I get the below error. For security reasons I delete the database each time the app quits (I save an encrypted copy) and on the next launch I decrypt this DB.

The code used to initialise the PersistentStoreCoordinator.

NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = @{
                          NSMigratePersistentStoresAutomaticallyOption : @YES,
                          NSInferMappingModelAutomaticallyOption : @YES
                          };


// Check if we need a migration
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
NSManagedObjectModel *destinationModel = [_persistentStoreCoordinator managedObjectModel];
BOOL isModelCompatible = (sourceMetadata == nil) || [destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
if (! isModelCompatible) {
    // We need a migration, so we set the journal_mode to DELETE
    options = @{NSMigratePersistentStoresAutomaticallyOption:@YES,
                NSInferMappingModelAutomaticallyOption:@YES,
                NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}
                };
}

NSPersistentStore *persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
if (! persistentStore) {


    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents = [paths objectAtIndex:0];
    NSString *databasePath = [documents stringByAppendingPathComponent:@"Store"];
    NSString *sqlite = [databasePath stringByAppendingPathComponent:@"myDatabase.sqlite"];

    [[NSFileManager defaultManager] removeItemAtPath:sqlite error:nil];

    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    //abort();
}

// Reinstate the WAL journal_mode
if (! isModelCompatible) {
    [_persistentStoreCoordinator removePersistentStore:persistentStore error:NULL];
    options = @{NSMigratePersistentStoresAutomaticallyOption:@YES,
                NSInferMappingModelAutomaticallyOption:@YES,
                NSSQLitePragmasOption: @{@"journal_mode": @"WAL"}
                };
    [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
}

return _persistentStoreCoordinator;

When I try to initialise the persistentStoreCoordinator, I get the bellow error.

Unresolved error Error Domain=NSCocoaErrorDomain Code=259 "The operation couldn’t be completed. (Cocoa error 259.)" UserInfo=0x15df4dc0 {NSUnderlyingException=File at path does not appear to be a SQLite database: /var/mobile/Applications/9B623099-5591-4C55-BA83-77A057B94690/Documents/Store/myDatabase.sqlite}, {

NSUnderlyingException = "File at path does not appear to be a SQLite database: /var/mobile/Applications/9B623099-5591-4C55-BA83-77A057B94690/Documents/Store/myDatabase.sqlite";}

The strange part is that on iOS7.0.6, the upgrade scenario worked fine both on Dev and Release configurations but on iOS7.1 it seems to be working only on Dev configuration. I have tired deleting the WAL and SHM files as well but to no avail.


Solution

  • The error message is very telling here. It says:

    NSUnderlyingException = "File at path does not appear to be a SQLite database: /var/mobile/Applications/9B623099-5591-4C55-BA83-77A057B94690/Documents/Store/myDatabase.sqlite";}

    I was thinking that this may be due to a mismatch between the updated core data sqlite and the WAL files, but then turns out the error was more to do with a key mismatch during the encryption-decryption process. This was my first guess and I had asked my colleague to check this but he somehow validated this and we disapproved this theory.

    Our encryption works by saving a part of the run time generated encryption key into the keychain. Now the premise is that the value in keychain before app upgrade and after the upgrade persists. However due to a wrong Bundle identifier being used, the upgraded app did not get the value from the keychain and it created its own runtime value.

    Values stored in the keychain are shared between apps and app updates only if the complete bundle identifier is same.

    app1 had a bundle identifier of BundleIdentifierPrefix1.com.yourcompany.appname app1.1 had a bundle identifier of BundleIdentifierPrefix2.com.yourcompany.appname

    Since the reverse domain was same, the app was installed as an update but since the prefix was different the key chain values were not shared.