Data migration when iCloud is turned on / off

Local account

From WWDC 2013 207 session on Core Data and iCloud:

You provide us with one storage URL inside the local sandbox application, and then we create an opaque container with an entry inside this for each account in the system, including the local account, which is our term for what happens when there is no iCloud account on system. This is a special store that is managed by Core Data, so you donโ€™t have to do anything because your user does not have an iCloud account.

On iOS 7 / OS X 10.9, Core Data with iCloud automatically uses a local account for situations in which iCloud is turned off. Unlike backup storage (used when iCloud is turned on but not available), the local account will be completely replaced by the iCloud account when the service is turned on, without merging. Data in the local account is only available if iCloud is turned off. This happens when:

  • No iCloud account.
  • There is an iCloud account, but Docs & Data is disabled.
  • There is an iCloud account, but the app is disabled in Docs & Data.

The above, which I understand from the experiments. Please correct me if I am wrong.

When data disappears

Used as is, the user interface of the local account is terrible . If you add data to the application with iCloud and then turn it on, the data will disappear and you might think that it was deleted. If you add data to the application with iCloud and then disable it, the data will also disappear.

I saw examples that try to get around this by adding (more) iCloud settings to the app and managing their own "local" storage (and not the one provided by iCloud). This stinks of duplicate work for me.

Using a local account to transfer data

How about this approach?

  • Always use Core Data and iCloud, whether iCloud is on or off.
  • When iCloud switches from one to the other, ask users if they want to combine the local account with the iCloud account . If yes, merge, delete duplicates, local priorities and clear the local account.
  • When iCloud goes off, ask users if they want to combine iCloud storage with their local account . If so, merge and remove duplicates with iCloud priority.

This is similar to what Reminders does. However, Reminders asks the user about data migration directly from iCloud settings, which we cannot do for developers.

Questions

1) Does this approach have any flaws or borderline cases that may not be obvious at first glance? Perhaps we should not use a local account created by iCloud like this.

2) Is NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification detect all possible on and off for iCloud transitions?

3) Could you fulfill the user invitation and merge between NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification or collect all the information in them and wait for the storage to change? I ask because these notifications seem to be sent in the background, and blocking them to perform a potentially lengthy operation may not be what Core Data expects.

+7
ios core-data macos icloud core-data-migration
source share
2 answers

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) { // Create a copy because we can't add an ubiquity store as a local store without removing the ubiquitous metadata first, // and we don't want to modify the original ubiquity store. importPersistentStoreURL = [self copyPersistentStoreAtURL:importPersistentStoreURL]; } if (!importPersistentStoreURL) return; // You might want to use a different concurrency type, depending on how you handle migration and the concurrency type of your current context NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; importContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; NSPersistentStore *importStore = [importContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:importPersistentStoreURL options:@{NSPersistentStoreRemoveUbiquitousMetadataOption : @(YES)} error:nil]; [self importContext:importContext intoContext:_managedObjectContext]; if (!isLocal) { [[NSFileManager defaultManager] removeItemAtURL:importPersistentStoreURL error:nil]; } } 

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.

+3
source share

I think you misunderstood what was said in the 207th session.

Core Data will not automatically create local and iCloud storage for you, not those that will sync data when your iCloud account is disconnected anyway. Depending on which user you choose, you need to create the storage either using the NSPersistentStoreUbiquityNameKey parameter (for iCloud storage) or not use it (for local storage).

Since the default security setting for new Data & Documents applications is enabled when your application is installed, you MUST ask the user if they want to use iCloud or not. Try using the Apple Pages app.

If the user subsequently changes the preference setting, your application must transfer the storage to or from iCloud.

The Core Data element is processed automatically, if you switch your iCloud account (log out and log in with a different account), then the application will work with any Core Data storage that could be created during the login to this account.

See the explanatory note below for a clear indication that iCloud storage is deleted when the account leaves. He's gone, kaput, a dead parrot. Therefore, while you get the opportunity to save only change logs, they remain local if the account will be used again in the future.

You simply realize your desire to change handlers and respond to Stores. NSPersistentStoreCoordinator will change and will notify you automatically when we need to change the permanent storage file, because there is a new account in the system.

Of course, you can call NSManagedObjectContext save and NSManagedObjectContext reset.

Now, when you do this, we will remove the repository from the coordinator as in the asynchronous installation process, and then we will send you the NSPersistentStoreCoordinator repository changed the notification, as well as the asynchronous setup, and you can start working as usual.

Now let's talk about this in more detail.

When you receive the NSPsistentStoreCoordinator, the stores change the notification, the persistent storage is still available for use, and therefore, unlike what we advised you last year, when you had to immediately remove the persistent storage and destroy the managed object context, you can still write to the context of the managed object and these changes will be persistently imported locally into the account if everyone returns .

This means that although your user changes will not go to iCloud immediately, if they ever record again, they will be there and waiting .

Finally, all of these storage files will be managed by Core Data and this means that we could delete them at any time.

Each store will be deleted after its account disappears , because we can rebuild the file from the cloud.

So, we want to free up as much disk space as possible for your application to use and not store old storage files may take additional resources.

and a little further

We are also introducing a new option that will help you back up or local copies of iCloud persistent storage called NSPersistentStore Remove the ubiquitous metadata option.

Removes all associated metadata from iCloud storage; which means everything that we write in the metadata dictionary, as well as the storage file itself , and this is important if you want to use the migration API to create backups or local copies in the persistent storage you want to open without iCloud settings .

Also take a look at this error link for Tim Rodley's book.

http://timroadley.com/2014/02/13/learning-core-data-for-ios-errata/

If you are logged into iCloud, and then the user changed the application preferences setting (not the same as the Data & Documents security setting) to disconnect iCloud from your application, ask the user if they want to transfer the existing iCloud to store locally (repeat try using the pages and see what messages you get).

I posted an example application that does all of this here. Take a look at the video to see the expected behavior. http://ossh.com.au/design-and-technology/software-development/

Some of the features of the sample applications include:

Features include:

  • Sample iOS and OSX Core Data Applications with iCloud Integration.
  • Using master data storage Local or iCloud
  • Includes the Settings Package (note that this creates the settings page in the Settings application), which includes:
    • Use iCloud preference setting (ON or OFF)
    • Configure backup settings (ON or OFF)
    • Show application version and build number
  • Represents user storage options when Use iCloud preference changes to ON
  • Migrates master data storage to and from iCloud depending on user preference settings and answers to prompts
  • Detect iCloud storage deletion from another device and clean up by creating a new empty iCloud storage
  • Checks existing iCloud files when transferring local storage to iCloud and asks the user whether to merge or delete data in local storage if an iCloud file exists.
  • Backs up the master data store if Back up is set to ON. The backup file name is persistentStore_Backup_yyyy_MM_dd_HH_mm_ss. To use it:
    • set the backup mode to ON and the next time the application is activated, it will backup the current Core data store and reset is preferable to OFF
    • The file can be copied to a PC or Mac from iTunes.
    • to restore, simply install the application on the use of local files (use the iCloud OFF privilege) and replace the persistentStore file with the necessary backup file (note that the file must be called persistentStore ).
  • Editing a record and saving / canceling editing in the detail view
  • Asynchronous opening of the master data warehouse to ensure long-term migrations does not block the main stream and leads to termination of the application
  • Loading data into a background thread using Pull to Refresh in the main UITableView to start another background thread (you can run multiple background threads at the same time, take care!)
  • Show related objects in detailView using UITableView, fetchedResultsController and predicate for filter selection
  • Download seed data, if no storage already exists , checks if the iCloud file was created by another device
  • ICloud upload / download indicator, network activity indicator turns on when Core Data transaction logs are to be synchronized, synchronized, imported, or background tasks performed.
  • Sidebar style interface with a few basic and detailed views for iOS and OS X applications
  • Backup File Manager , which allows you to create backups, copy backup files to iCloud, send and receive backup files by e-mail and restore from a backup file.
+6
source share

All Articles