I will try to answer my question, partly in order to organize my thoughts and partially answer @DuncanGroenewald.
1) Does this approach have any drawbacks or border cases that may not be obvious at first glance?
Yes. Local and iCloud accounts are managed by Core Data and can be deleted at any time.
In practice, I do not think that the local account store will be deleted, since it cannot be recreated from iCloud.
As for iCloud account stores, I can see two scenarios in which they can be deleted: a) to free space after disconnecting an iCloud user or b) because the user requested it by choosing Settings> iCloud> Delete All "
If the user requested it, you can argue that data migration is not a concern.
If there was free space, then yes, this is a problem. However, the same problem exists in any other method, since your application does not wake up when iCloud account stores are deleted.
2) Are NSPsistentStoreCoordinatorStoresWillChangeNotification and NSPsistentStoreCoordinatorStoresDidChangeNotification sufficient to detect all possible on and off for iCloud transitions?
Yes. This requires that you always create persistent storage with NSPersistentStoreUbiquitousContentNameKey , regardless of whether iCloud is on or off. Like this:
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:@{ NSPersistentStoreUbiquitousContentNameKey : @"someName" } error:&error];
Actually listening to NSPersistentStoreCoordinatorStoresDidChangeNotification (as shown below). This will be called when the repository is added at startup or changed at runtime.
3) Would you make a user request and merge between NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification, or collect all the information in them and wait until the store is changed? I ask because these notifications seem to be sent in the background, and blocking them to perform a potentially lengthy operation might not be what Core Data expects.
Here is how I would do it in NSPersistentStoreCoordinatorStoresDidChangeNotification .
Since this notification is sent both at startup and when the repository changes at run time, we can use it to save the current token of the repository URL and the ubiquity (if any).
Then we check if we are in the on / off transition script and transfer the data accordingly.
For brevity, I do not include UI code, user prompts or error handling. You must ask (or at least inform) the user before performing the migration.
- (void)storesDidChange:(NSNotification *)notification { NSDictionary *userInfo = notification.userInfo; NSPersistentStoreUbiquitousTransitionType transitionType = [[userInfo objectForKey:NSPersistentStoreUbiquitousTransitionTypeKey] integerValue]; NSPersistentStore *persistentStore = [userInfo[NSAddedPersistentStoresKey] firstObject]; id<NSCoding> ubiquityIdentityToken = [NSFileManager defaultManager].ubiquityIdentityToken; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if (transitionType != NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) { // We only care of cases if the store was added or removed NSData *previousArchivedUbiquityIdentityToken = [defaults objectForKey:HPDefaultsUbiquityIdentityTokenKey]; if (previousArchivedUbiquityIdentityToken) { // Was using ubiquity store if (!ubiquityIdentityToken) { // Changed to local account NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey]; NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString]; [self importPersistentStoreAtURL:previousPersistentStoreURL isLocal:NO intoPersistentStore:persistentStore]; } } else { // Was using local account if (ubiquityIdentityToken) { // Changed to ubiquity store NSString *urlString = [defaults objectForKey:HPDefaultsPersistentStoreURLKey]; NSURL *previousPersistentStoreURL = [NSURL URLWithString:urlString]; [self importPersistentStoreAtURL:previousPersistentStoreURL isLocal:YES intoPersistentStore:persistentStore]; } } } if (ubiquityIdentityToken) { NSData *archivedUbiquityIdentityToken = [NSKeyedArchiver archivedDataWithRootObject:ubiquityIdentityToken]; [defaults setObject:archivedUbiquityIdentityToken forKey:HPModelManagerUbiquityIdentityTokenKey]; } else { [defaults removeObjectForKey:HPModelManagerUbiquityIdentityTokenKey]; } NSString *urlString = persistentStore.URL.absoluteString; [defaults setObject:urlString forKey:HPDefaultsPersistentStoreURLKey]; dispatch_async(dispatch_get_main_queue(), ^{ // Update UI }); }
Then:
- (void)importPersistentStoreAtURL:(NSURL*)importPersistentStoreURL isLocal:(BOOL)isLocal intoPersistentStore:(NSPersistentStore*)persistentStore { if (!isLocal) {
Data migration is done in importContext:intoContext . This logic will depend on your model and duplication and conflict policies.
I canโt say if this can have unwanted side effects. Obviously, this can take some time depending on the size and data of the persistent storage. If I find any problems, I will edit the answer.