13


I have an app already in the App Store that uses core data to save data.
Now, when iOS 8 is about to come out I wanna add a widget to it, thus I must use App Groups to share data between the binaries.
One problem though - I need to change the store location to support App Groups to all the existing users.
I wrote the following code, trying to move the store to the new path:

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *oldStoreURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    oldStoreURL = [oldStoreURL URLByAppendingPathComponent:@"Schooler.sqlite"];


    NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.schooler.mycontainer"];
    storeURL = [storeURL URLByAppendingPathComponent:@"Schooler.sqlite"];


    if([[NSFileManager defaultManager] fileExistsAtPath:oldStoreURL.path] == YES && [[NSFileManager defaultManager] fileExistsAtPath:storeURL.path] == NO)
    {
        // Prior today extension - Need to move to new directory
        NSError *error = nil;
        if([[NSFileManager defaultManager] moveItemAtURL:oldStoreURL toURL:storeURL error:&error] == YES)
            NSLog(@"Migrated successfully to new database location.");
        else
            NSLog(@"error: %@",error);
    }

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

The output is always "Migrated successfully to new database location.", although all the data that was saved on the app before has been deleted, As if it created a new database instead of just moving it.

What causes the problem? How should I fix it?
Thank you.

Noam Solovechick
  • 1,127
  • 2
  • 15
  • 29
  • Please checkout below post for a solution in swift. https://stackoverflow.com/questions/27253566/coredata-move-to-app-group-target/47551887#47551887 – Pranav Gupta Nov 29 '17 at 14:24

2 Answers2

17

A Core Data NSSQLiteStoreType store created with the default options is actually several files, as described in Technical Q&A 1809: New default journaling mode for Core Data SQLite stores in iOS 7 and OS X Mavericks. This is important to remember when attempting to move a store outside of a migration process, and is the source of your issue - you are moving one file when you need to be moving all of them. Moving the files individually outside of Core Data and without the benefits of a file coordinator is not recommended, however. It's much better to use a migration instead.

A migration will take the data from the source store and migrate it to your new store location, essentially replicating the old data at the new location. The old data will still exist on the filesystem. In your application, you should perform the migration as you are now, but do not attempt to move the old data to the new location yourself - that is where things are going wrong.

Instead of moving files around yourself, you can rely on a migration to move the data for you. First, add a store to the persistent store coordinator with the URL of the source data. Then you will perform a migration to move that data to the new URL

NSPersistentStore   *sourceStore        = nil;
NSPersistentStore   *destinationStore   = nil;
NSDictionary        *storeOptions       = @{ NSSQLitePragmasOption : @{ @"journal_mode" :
  @"WAL" } };

// Add the source store
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldStoreURL options:storeOptions error:&error]){
    // Handle the error
} else {
    sourceStore = [coordinator persistentStoreForURL:oldStoreURL];
    if (sourceStore != nil){
        // Perform the migration
        destinationStore = [coordinator migratePersistentStore:sourceStore toURL:storeURL options:storeOptions withType:NSSQLiteStoreType error:&error];
        if (destinationStore == nil){
            // Handle the migration error
        } else {
            // You can now remove the old data at oldStoreURL
            // Note that you should do this using the NSFileCoordinator/NSFilePresenter APIs, and you should remove the other files
            // described in QA1809 as well.
        }
    }
}

Once the migration has completed you can delete the old files. The example here explicitly specifies the SQLite journal options, this is to ensure that if the default options are changed in the future the code will still work. If you are using different options, you should use those instead.

Dev
  • 7,027
  • 6
  • 37
  • 65
quellish
  • 21,123
  • 4
  • 76
  • 83
  • No worries Noam, glad I could help. Good luck with your extension and app! – quellish Sep 08 '14 at 18:25
  • 1
    For deleting those remaining files one should rather use NSFileManager's removeItemAtURL:https://developer.apple.com/library/mac/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/ManagingFIlesandDirectories/ManagingFIlesandDirectories.html#//apple_ref/doc/uid/TP40010672-CH6-SW11. Why do you recommend NSFileCoordinator/NSFilePresenter? – Vilém Kurz Oct 12 '14 at 18:24
  • Core Data uses file coordination when accessing files in the default store types. If you are going to access these files directly - again, not recommended - you should only do so using file coordination. This ensures you are not modifying files while the Core Data framework is using them. NSFileManager would be used *within* the file coordination block in this case. Again, the preferred way to do any of this is through Core Data APIs, but if you must access the files directly for some bizarre reason, make sure you do so using the file coordination APIs. – quellish Oct 12 '14 at 20:36
  • @quellish I am using the above code to try to move a store from my app's Documents directory to a group container, however `[coordinator migratePersistentStore:sourceStore toURL:storeURL options:storeOptions withType:NSSQLiteStoreType error:&error]` always returns nil and error is also nil. I've tried with different toURL parameters with the same result. The options parameter I'm passing is what I've always been using (`@{ NSPersistentStoreUbiquitousContentNameKey : @"my~persistent~store~ubiquitous~content~name" }` – rick Mar 01 '15 at 06:40
  • Yes, I saw your several posts on the apple forums. In most cases iCloud ubiquitous content will not work with app groups. There are several past threads at the apple forum that detail why – quellish Mar 01 '15 at 07:17
  • Thank you. I hadn't seen that information previously related to ubiquitous content in app groups. Migrating it to a local store to the app group worked flawlessly. Think I'll end up moving away from Core Data with iCloud and just use Core Data by itself. – rick Mar 01 '15 at 18:39
  • QA1809 recommends using @"journal_mode" : @"DELETE" for backup, restore (and implicitly, moves). Any reason you recommend using @"WAL" here? – stephent Sep 16 '15 at 21:12
  • 1. Original question was about WAL. 2. Like with everything in CoreData, always be explicit about options when accessing a store. The defaults can and will change, creating problems like this. – quellish Sep 16 '15 at 21:24
2

In case having a version in Swift would be helpful:

    let oldPersistentStoreURL: URL = ...
    let sharedPersistentStoreURL: URL = ...
    let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]    // + any database-specific options

    if FileManager.default.fileExists(atPath: oldPersistentStoreURL.path) {
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: oldPersistentStoreURL, options: options)
            if let sourceStore = coordinator.persistentStore(for: oldPersistentStoreURL) {
                let _ = try coordinator.migratePersistentStore(sourceStore, to: sharedPersistentStoreURL, options: options, withType: NSSQLiteStoreType)
                // If migration was successful then delete the old files
            }
        } catch {
            error.logErrors()
        }
    }
Paul King
  • 1,881
  • 20
  • 23