9

I'm currently struggling with Core Data iCloud migration.

I want to move a store from an iCloud ubiquity container (.nosync) to a local URL. The problem is that whenever I call something like this:

[self.persistentStoreCoordinator migratePersistentStore: currentiCloudStore 
                                                  toURL: localURL 
                                                options: nil 
                                               withType: NSSQLiteStoreType 
                                                  error: &error];

I get this error:

-[NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error:](1055): CoreData: Ubiquity:  Error: A persistent store which has been previously added to a coordinator using the iCloud integration options must always be added to the coordinator with the options present in the options dictionary. If you wish to use the store without iCloud, migrate the data from the iCloud store file to a new store file in local storage. file://localhost/Users/sch/Library/Containers/bla/Data/Documents/tmp.sqlite. This will be a fatal error in a future release

Anyone ever seen this? Maybe I'm just missing the right migration options?

Arpit
  • 6,212
  • 8
  • 38
  • 69
schmok
  • 111
  • 1
  • 3
  • Is this a UIManagedDoc or a standalone database? A UIManagedDoc has a plist at the root of the bundle. – Warren Burton Oct 05 '12 at 21:47
  • No UIManagedDoc. It's a 'shoe box' core data setup. – schmok Oct 05 '12 at 23:07
  • Forgot to mention that I'm getting this error in a mac application (10.8.2). Not tried it on iOS yet. – schmok Oct 05 '12 at 23:12
  • Ah . I get it . Try just doing a straight copy of the file rather than a managed migrate. You can then reboot a new PSC with that file. – Warren Burton Oct 06 '12 at 08:43
  • Yep, that was in fact my first attempt in order to backup from iCloud to local. Without luck. I get the same error when using addPersistentStore: with a file I just copied. It's writing a lot of iCloud related information to the stores metadata. Unfortunately, I don't seem to be able to get rid of that. – schmok Oct 06 '12 at 11:17
  • Do you include the std iCloud options when you set up the PSC prior to migrating? Perhaps you need to setup without them if you intend migrating. – Warren Burton Oct 07 '12 at 08:09
  • The only way I know to set up a store is by calling "addPersistentStoreWithType:configuration:URL:options:error". Whenever I call this with an iCloud database I get this error. Is there a way to reset or remove options from an existing store? – schmok Oct 08 '12 at 07:07
  • 2
    I am getting the same message. I have two versions of an app that uses the "shoe box" model as well. One version has iCloud, one does not. I had been able to copy the database between apps with no issues. Now with iOS 6 I get the message as well. I am anxious to hear any solution. – Phill Campbell Oct 24 '12 at 14:55
  • No solution yet. What I did as a workaround was just creating a new store and manually copied the data from the old store to the new store. Not very elegant, I know. – schmok Oct 25 '12 at 14:23

4 Answers4

2

My guess is that, based on the error message, by setting your options to nil, the PSC is unable to move the store. You probably need to get the dictionary of options for the original store and pass those along instead of setting them to nil.

Mike
  • 51
  • 3
2

YES, I have this question too.

I want to turn a iCloud store to a local store.


Solution 1 :Moving managedObjects one-by-one to the localStore.

But if you have a large database, it will be so slow.

So I found a second solution yesterday.


Solution 2: Editing the metadata of the iCloud store,

and saving it to the new location.

After you remove "com.apple.coredata.ubiquity.*" keys in metadata, you'll get a fully local store.


Here is my code for solution 2:

There are some properties already set:

@property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, strong) NSManagedObjectContext *context;

@property (nonatomic, strong) NSPersistentStore *iCloudStore;
//represent the iCloud store already using 
//(after [coordinator addPersistentStore] you get this NSPersistentStore)

@property (nonatomic, strong) NSURL *iCloudStoreURL;
//represent the iCloud store real location
//(it is the URL you send to the [coordinator addPersistentStore])

@property (nonatomic, strong) NSURL *iCloudStoreLocalVersionURL;
//represent the location of local version store you want to save

And the migrate method:

-(void)migrateCloudStoreToLocalVersion
{
    if(!self.iCloudStore)
        return;

    // remove previous local version
    [FILE_MANAGER removeItemAtURL:self.iCloudStoreLocalVersionURL
                            error:nil];

    // made a copy from original location to the new location
    [FILE_MANAGER copyItemAtURL:self.iCloudStoreURL
                          toURL:self.iCloudStoreLocalVersionURL
                          error:nil];

    //prepare meta data
    NSDictionary *iCloudMetadata = [self.coordinator metadataForPersistentStore:self.iCloudStore].copy;

    NSMutableDictionary *localVersionMetadata = iCloudMetadata.mutableCopy;
    for(NSString * key in iCloudMetadata){
        if([key hasPrefix:@"com.apple.coredata.ubiquity"]){
            [localVersionMetadata removeObjectForKey:key];
        }
    }

    //modify iCloud store
    [self.coordinator setMetadata:localVersionMetadata forPersistentStore:self.iCloudStore];
    [self.coordinator setURL:self.iCloudStoreLocalVersionURL forPersistentStore:self.iCloudStore];

    //save to the localVersion location
    [self.context save:nil];

    //restore iCloud store
    [self.coordinator setMetadata:iCloudMetadata forPersistentStore:self.iCloudStore];
    [self.coordinator setURL:self.iCloudStoreURL forPersistentStore:self.iCloudStore];
}

Then you can use the iCloudStoreLocalVersionURL to using the local version store.

You can use this local version store as local store, without any error.

Note:

Notice the NSStoreUUIDKey in the metadata,

you can optional replace it for the new store.

To mike:

The problem is:

If we use full iCloud options on adding a iCloud store, we'll get all things right but it remains a iCloud store. We here want to turn a iCloud store to local store.

If we add some options except iCloud options, we'll get an error and cannot save any change to this store.

So your answer is not for this problem.

frogcjn
  • 819
  • 5
  • 21
  • Sounds pretty good! I implemented the first solution a few months back. I will play with the second one as soon as I can. Thanks for your sample code. – schmok Jan 03 '13 at 10:08
  • Thanks for the answer! When should I call the migration method? Since if a user deletes the app and re-install it, iCloud needs time to retrieve data from cloud – hyouuu Mar 04 '15 at 08:43
1

This works for me just fine. I am using NSPersistentDocument and call this method if the user selects Save As menu option. Its also worth noting that I set my own metadata on the file in order to know whether it is synced via iCloud or not. You have to know that in order to pass in the right options when setting up the pac prior to actually opening the store itself. I don't believe there is any API provided for doing this any other way.

// File is NEVER iCloud enabled when we do this.
- (bool)buildNewStoreAtURL:(NSURL*)newURL type:(NSString *)typeName error:(NSError **)error {

    //FLOG(@"buildNewStoreAtURL:type: called");

    NSError *myError;

    // We only have one store
    NSPersistentStore *currentStore = [self.managedObjectContext.persistentStoreCoordinator.persistentStores objectAtIndex:0];
    NSDictionary *currentOptions = currentStore.options;

    // We need to create new options for the new document if it is currently synced via iCloud.
    NSMutableDictionary *newOptions = [[NSMutableDictionary alloc] initWithDictionary:currentOptions];
    [newOptions setObject:@"DELETE" forKey:@"JOURNAL"];

    // Remove any iCloud options
    [newOptions removeObjectForKey:NSPersistentStoreUbiquitousContentNameKey];

    // Instruct to remove any Core Data iCloud metadata
    [newOptions setObject:[NSNumber numberWithBool:YES] forKey:NSPersistentStoreRemoveUbiquitousMetadataOption];


    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;

    //FLOG(@"   create a new store at the required URL");
    NSPersistentStore *newStore = [psc migratePersistentStore:currentStore toURL:newURL options:newOptions withType:typeName error:&myError];

    if (newStore) {
        //FLOG(@"   store seems OK");
        // Set our own metadata flags so we can tell of the file is synced via iCloud
        // before we open the store (we have to pass in the right ubiquity name if it is)
        NSDictionary *dict = [self getiCloudMetaDataForStore:[psc metadataForPersistentStore:newStore] iCloud:NO ubiquityName:nil];
        [psc setMetadata:dict forPersistentStore:newStore];
        return YES;
    }
    else {

        FLOG(@" problem creating new document");
        FLOG(@"  - error is %@, %@", myError, myError.userInfo);
        *error = myError;
        return NO;
    }

}
Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
1

As of iOS 7, there is a much easier and more logical way to migrate stores while dropping iCloud ubiquity, simply pass the NSPersistentStoreRemoveUbiquitousMetadataOption option:

NSDictionary *options = [NSDictionary dictionaryWithObject:@YES
                                                    forKey:NSPersistentStoreRemoveUbiquitousMetadataOption];
[self.persistentStoreCoordinator migratePersistentStore: currentiCloudStore 
                                              toURL: localURL 
                                            options: nil 
                                           withType: NSSQLiteStoreType 
                                              error: &error];

This will migrate the store to the local URL, and drop all iCloud metadata, essentially what @frogcjn did manually.

Smeedge
  • 190
  • 1
  • 11