iCloud Core Data Sync Setting - ios

I am working on an application that incorporates iCloud syncing of its Core Data store (simple application with a single store/model/context). Since the store also contains image data, it has the possibility for getting quite large and so I would like to add a setting to allow the user to disable the syncing if they wish. I have looked at some sample code for using Core Data in both scenarios, and it looks to me that the only real difference between running with iCloud enabled and disabled are the options passed to the NSPersistentStoreCoordinator when it is added. As such, I had though about doing something like this:
NSPersistentStoreCoordinator *psc;
NSDictionary* options;
//Set options based on iCloud setting
if ([enableSwitch isOn]) {
options = [NSDictionary dictionaryWithObjectsAndKeys:
#"<unique name here>", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
} else {
options = nil;
}
//Add the coordinator
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
//Handle error
}
So, I have a couple of questions about the above:
Is my assumption correct, or is there more between the two states that needs to be different?
Often in examples this code is called in the application delegate, so it usually is only called once per application run. Is there a good strategy to responding to the necessary change on demand as the user toggle the setting?
Thanks!

This is the solution that I came up with, and it is working pretty well. Basically the code below is placed in a method that you can call when your application starts up, or anytime the your user-facing "Enable iCloud Sync" setting changes. All that needs to change between the two operation modes is the NSPersistentStore instance, the underlying model file and the coordinator need no changes.
My application only has one persistent store to worry about, so upon calling this method, it just removes all stores currently attached to the coordinator, and then creates a new one with the appropriate options based on the user settings.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DataModel.sqlite"];
NSError *error = nil;
NSArray *stores = [__persistentStoreCoordinator persistentStores];
for (int i=0; i < [stores count]; i++) {
[__persistentStoreCoordinator removePersistentStore:[stores objectAtIndex:i] error:&error];
}
//Determine availability. If nil, service is currently unavailable
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
if (!cloudURL) {
NSLog(#"iCloud currently unavailable.");
}
//Check if user setting has been set
if (![[NSUserDefaults standardUserDefaults] objectForKey:kCloudStorageKey]) {
//Set the default based on availability
BOOL defaultValue = (cloudURL == nil) ? NO : YES;
[[NSUserDefaults standardUserDefaults] setBool:defaultValue forKey:kCloudStorageKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
//Set options based on availability and use settings
BOOL cloudEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kCloudStorageKey];
NSDictionary *options = nil;
if (cloudEnabled && cloudURL) {
NSLog(#"Enabling iCloud Sync in Persistent Store");
options = [NSDictionary dictionaryWithObjectsAndKeys:
#"<awesome.unique.name.key>", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
}
//Add the store with appropriate options
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
If you have an application that utilizes multiple stores, you may need to keep references around to the stores that you want to enable/disable sync on so you can have a smarter approach rather than just blowing them all away every time.

Related

SQLCipher and CoreData issue: CoreData could not fulfill a fault for

I have an issue with SQLCipher db encryption and CoreData:
When I use persistent store coordinator with SQLCipher, it always crash with fault one-to-many relationship after first app relaunch.
So when I first time launch the app, I create NSManagedObjects with relationships, then, when I save db, and reopen the app, it crash when I try to access to these relationship.
Without SQLCipher everything works fine.
Here is the code for SQLCipher persistent store initialize:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (!_persistentStoreCoordinator) {
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyApp.sqlite"];
NSDictionary *options = #{EncryptedStorePassphraseKey: #"MyApp",
EncryptedStoreDatabaseLocation: storeURL};
NSError *error;
_persistentStoreCoordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:[self managedObjectModel] error:&error];
if (error) {
NSLog(#"%#", error);
}
}
return _persistentStoreCoordinator;
}
Code where I create NSManagedObject:
- (id)createObjectWithClassName:(NSString *)name
{
NSManagedObject *object = [[NSClassFromString(name) alloc] initWithEntity:[NSEntityDescription entityForName:name inManagedObjectContext:self.context] insertIntoManagedObjectContext:self.context];
return object;
}
Finally I find the answer by myself.
I investigate different cases, and figured, that issue happens only in my Data Model.
Problem was that I have one property in NSManagedObject class with name "index".
Looks like SQLCipher internally use such property and there was conflicts with it.
Once I rename it to another name, everything works!
With SQLCipher make sure you have a brand new SQLite database. Trying to pragma the database with a key while it already has data for some reason just tries to decrypt it.
ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'secret'; -- create a new encrypted database
CREATE TABLE encrypted.t1(a,b); -- recreate the schema in the new database (you can inspect all objects using SELECT * FROM sqlite_master)
INSERT INTO encrypted.t1 SELECT * FROM t1; -- copy data from the existing tables to the new tables in the encrypted database
DETACH DATABASE encrypted;
--
//For persistentStoreCoordinator:
// Give modelName = #"MyApp.sqlite";
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator:(NSString*)modelName
{
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
#"PushNoticationModal.sqlite"
NSURL *storeUrl = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:modelName]];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error;
persistentStoreCoordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:[self managedObjectModel] error:&error];
if (error) {
NSLog(#"%#", error);
}
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
NSLog(#"persistentStoreCoordinator Error: %#,%#",error,[error userInfo]);
}
return persistentStoreCoordinator;
}

Migrating an iCloud Store to a Local Store and making sure the data is there through each app launch

Based on the question here: Migrate iCloud data to Local store and stopping iCloud from still responding regard moving an iCloud store over to a local store and making sure the iCloud notifications are disabled, I have the next scenario that I am stuck on.
Issue
Right now, when the user runs the app on the first device, they're asked at the start if they want to enable iCloud. If they select yes, the data is migrated over to iCloud. If the user connects in a second device, they're asked if they want to use iCloud at the start and if they select yes, data from the first device is synchronised across.
If the user on the second device decides they don't want to use iCloud anymore, they can navigate and turn off iCloud within the app for that device specifically. This in the back-end will migrate their data from the iCloud store to the Local Store. From the users point of view, all of their data is there (whether it's stored in the cloud or local doesn't matter to the user at this point).
The issue is when I migrate my data across, it successfully migrates over to the local iCloud store and successfully stops listening for iCloud notifications. That aspect works and that's in the stack overflow question that I previously asked (Migrate iCloud data to Local store and stopping iCloud from still responding).
The specific issue is that when the user re-launches the app, the app is now empty with no data. This is of course not the desired effect.
For completeness, the "migration code" is based in the other stack overflow question but I'm pasting it here for convenience:
- (void)migrateiCloudStoreToLocalStore {
NSLog(#"Migrate iCloudToLocalStore");
NSPersistentStore *store = self.persistentStoreCoordinator.persistentStores.lastObject;
//NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Envylope.sqlite"];
NSURL *storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
NSLog(#"Current Store URL (before iCloud to Local migration): %#", [storeURL description]);
NSDictionary *localStoreOptions = nil;
localStoreOptions = #{ NSPersistentStoreRemoveUbiquitousMetadataOption : #YES,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
NSPersistentStore *newStore = [self.persistentStoreCoordinator migratePersistentStore:store
toURL:storeURL
options:localStoreOptions
withType:NSSQLiteStoreType error:nil];
[self reloadStore:newStore];
}
- (void)reloadStore:(NSPersistentStore *)store {
NSLog(#"Reload Store");
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Envylope.sqlite"];
NSDictionary *localStoreOptions = nil;
localStoreOptions = #{ NSPersistentStoreRemoveUbiquitousMetadataOption : #YES,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
if (store) {
[self.persistentStoreCoordinator removePersistentStore:store error:nil];
}
[self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:localStoreOptions
error:nil];
storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
NSLog(#"Current Store URL (after iCloud to Local migration): %#", [storeURL description]);
NSLog(#"Done reloading");
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:#"MigratedFromiCloudToLocal"];
[[NSUserDefaults standardUserDefaults]synchronize];
All of the magic happens in the persistentStoreCoordinator.. it's messy, but I need to get this part working and then this can be cleaned up:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Envylope.sqlite"];
NSError *error = nil;
NSURL *iCloud = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier: nil];
NSDictionary *options = nil;
if ((iCloud) && (![[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudOn"]) && (![[NSUserDefaults standardUserDefaults]boolForKey:#"OnLatestVersion"]))
{
NSLog(#"In the persistent store, iCloud is enabled in the app device but not yet in the app and not on the latest version");
options = #{ NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
}
else if ((iCloud) && ([[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudOn"]) && ([[NSUserDefaults standardUserDefaults]boolForKey:#"OnLatestVersion"]))
{
NSLog(#"In the persistent store, iCloud is enabled, we're using iCloud from the Tutorial and we're on the latest version");
NSURL *cloudURL = [self grabCloudPath:#"iCloud"];
options = #{ NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
NSPersistentStoreUbiquitousContentURLKey: cloudURL, NSPersistentStoreUbiquitousContentNameKey: #"EnvyCloud"};
}
else if ((iCloud) && (![[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudOn"]) && ([[NSUserDefaults standardUserDefaults]boolForKey:#"OnLatestVersion"]))
{
NSLog(#"In the persistent store, iCloud is enabled, we're on the latest version, btu we're not using icloud in the App");
options = #{ NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
}
else
{
NSLog(#"iCloud is just not enabled");
options = #{ NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
return _persistentStoreCoordinator;
}
So in the scenario where the user enables iCloud in the app and then decides NOT to have iCloud anymore on this particular device, the iCloud notifications get removed, the store is migrated from the iCloud Store to the local store and a NSUserDefaults is set. On the next launch of the app.. the 3rd if statement evaluates as true in the persistentStoreCoordinator which is correctly saying we're on the latest version, iCloud is enabled (in the device) but not in the app, but then it shows the app with absolutely no entries whatsoever, kind of like starting fresh.
How do I overcome this and return the store that was just recently migrated?
Any thoughts would seriously be greatly appreciated.
No data upon app launch points to the persistent store coordinator.
Looks like the evaluation for the options dictionary is fine, but in either outcome you're always setting up the coordinator with the same persistent store URL.
Likewise, in the migration routine, you're migrating the persistent store object, but with the same URL. That can't work, as that's where the actual files are saved. You need a different URL for the local store and for the iCloud store, then you can correctly migrate the data across and choose to delete the URL you no longer need (after removing the store from the coordinator).
Setup the coordinator with the respective URL, depending on the outcome of your evaluation - and hopefully - you should see your data upon app launch.

Migrating existing Core Data to iCloud

I have a simple application which is a UITableView that gets populated by the user filling in some UITextFields in a different view. This information gets saved to CoreData and the UITableView gets updated using NSFetchedResultsController.
I'm now introducing iCloud synching into my environment. With reference to a previously asked question: Existing Core Data Database Not Uploading to iCloud When Enabling iCloud On My App , I am still having issues but it was too messy to update that question with my new code. I am referring specifically to the WWDC 2013 CoreData Sessions, as well as this. Because my app is iOS 7 only. I'm fairly new to iCloud development and am an intermediate programmer.
I am having some great difficulty migrating my existing data to iCloud when I update from the App Store (non-iCloud) version to the development (iCloud) version. New data syncs across perfectly.
I understand that I need to essentially take my existing store without iCloud and migrate it across to iCloud but it's just not working out for me.
Here's some code to illustrate what I'm working on:
Update
I have gone through and thought about this and have removed code that was redundant.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Planner.sqlite"];
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *url = [fm URLForUbiquityContainerIdentifier:nil];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (url)
{
NSLog(#"iCloud is enabled");
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
};
NSPersistentStore *localStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
[_persistentStoreCoordinator migratePersistentStore:localStore toURL:[self iCloudURL] options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
}
else
{
NSLog(#"iCloud is not enabled");
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self iCloudURL] options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"Reload" object:self userInfo:nil];
});
return _persistentStoreCoordinator;
}
I am no longer calling the migrateLocalStoreToiCloud method but it is here for reference:
/*-(void)migrateLocalStoreToiCloud
{
//assuming you only have one store.
NSPersistentStore *store = [[_persistentStoreCoordinator persistentStores] firstObject];
NSPersistentStore *newStore = [_persistentStoreCoordinator migratePersistentStore:store
toURL:[self iCloudURL]
options:[self iCloudStoreOptions]
withType:NSSQLiteStoreType
error:nil];
[self reloadStore:newStore];
}
*/
The storeURL, storeOptions, iCloudURL and iCloudStoreOptions code is:
- (NSURL *)iCloudURL
{
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *url = [fm URLForUbiquityContainerIdentifier:nil];
if (url) {
NSLog(#"iCloud access at %#", url);
} else {
NSLog(#"No iCloud access");
}
return url;
}
-(NSURL *)storeURL{
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Planner.sqlite"];
}
-(NSDictionary *)storeOptions{
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
return options;
}
-(NSDictionary *)iCloudStoreOptions{
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:#"PlannerStore" forKey:NSPersistentStoreUbiquitousContentNameKey];
return options;
}
Problem
When I run this code exactly as it is, I am not presented in the console with Using Local Storage : 1 and 0 at all, though the data is there.
If I change the persistentStoreCoordinator where it first adds the persistentStore to use [self iCloudStoreOptions] as the options, the Using Local Storage 1 and 0 displays, but the data does not on the device.
I'm following the guidance here and I have tried multiple variations of my code including not removing any persistentStore in the reloadStore method but I am completely stuck.
I have read so many posts but it seems that I just cannot find someone who is migrating data from a non-iCloud version to an iCloud version and with examples I have found, there's no code.
I have looked into this and while it is a great code set, I've downloaded the code and followed those guidelines where it doesn't remove any persistentStore but it still wouldn't work for me. I can't imagine what I'm trying to achieve is very complicated. I simply need to take the existing non-iCloud data and migrate it over to iCloud.
Update
With reading Apple's guide : https://developer.apple.com/LIBRARY/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html#//apple_ref/doc/uid/TP40013491-CH3-SW2 I can see that my migrateLocaltoiCloud method is currently wrong with the removing of the persistentStore, etc, but I can't figure out how to fix it. If I call this from the persistentStoreCoordinator, will it somehow interfere with the DidChangeNotification?
I'm really stuck on this and I would appreciate any guidance into the right direction
I am providing an answer here to my question because it's clearer than updating the question and after a lot of debugging, I have this working, to some extent.
With debugging the addPersistentStore and migratePersistentStore lines of code, I came up with:
NSPersistentStore *localStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
NSLog(#"The localStore is %#", localStore);
NSPersistentStore *migratedStore = [_persistentStoreCoordinator migratePersistentStore:localStore toURL:[self iCloudURL] options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
NSLog(#"The migrationStore is %#", migratedStore);
The NSLog value of localStore is: The localStore is <NSSQLCore: 0x13451b100> (URL: file:///var/mobile/Applications/DDB71522-C61D-41FC-97C7-ED915D6C46A4/Documents/Planner.sqlite)
The NSLog value of migratedStore is: (null)
With debugging, I could see essentially the same thing, that migrateStore was always null.
I changed the code slightly:
NSPersistentStore *migratedStore = [_persistentStoreCoordinator migratePersistentStore:localStore toURL:storeURL options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
For the URL to be the storeURL and suddenly, everything started working, where storeURL:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Planner.sqlite"];
My outputs were:
The localStore is <NSSQLCore: 0x14ee09b50> (URL: file:///var/mobile/Applications/CC0F7DA0-251F-46D6-8E43-39D63062AED2/Documents/Planner.sqlite)
[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~5BD59259-7758-42FB-986F-DDD173F75ED3:EnStor
The migrationStore is <NSSQLCore: 0x157e260b0> (URL: file:///var/mobile/Applications/A08D183D-ECD4-4E72-BA64-D21727DE7A3E/Documents/CoreDataUbiquitySupport/mobile~CB6C3699-8BF1-47DB-9786-14BCE1CD570A/EnStor/771D2B0B-870A-428E-A9A8-5DAE1295AF53/store/Planner.sqlite)
The Using Local Store went from 1 to 0 and my data from the App Store version of the app continued to be there through the migration to the new version. This was the output the first time I ran the app after the upgrade.
This is great news.
Every subsequent time I ran the app though, the data continued to migrate and so I would end up with duplicate results.
The issue also is the fact that when adding a new device, it doesn't pull down the information to the new device.
I don't quite understand why using storeURL as the url for migratePersistentStore worked?
I also don't want each new device to basically add a new persistent store if one already exists and I don't know how I would get around this and also I don't want the migration to occur again and again. It should only occur the one time.
I'm getting there, but still a lot of work to be done!

Enable / disable iCloud sync of Core Data while app is running

How can I add an option to enable and disable iCloud sync for Core Data (iOS7)?
Here are my thoughts / attempts:
To disable iCloud sync:
NSDictionary *options = #{NSPersistentStoreUbiquitousContentNameKey: #"MYStore"};
[NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:storeUrl options:options error:&error];
However, I think this may delete all the data from iCloud? I don't want that.
To enable iCloud sync:
NSDictionary *options = #{NSPersistentStoreUbiquitousContentNameKey: #"MYStore"};
[__persistentStoreCoordinator lock];
result = [__persistentStoreCoordinator migratePersistentStore:result toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error];
[__persistentStoreCoordinator unlock];
In this code, I'm attempting to add options with the NSPersistentStoreUbiquitousContentNameKey key so that it starts syncing with iCloud. However, I don't want to move the store to a new location. Is this code proper?
How can I enable / disable iCloud while the app is running?
Below are the methods I use to enable or disable iCloud sync (OS X), essentially the store gets rebuilt each time, which is not the same as enabling and disabling iCloud sync while the app is running. So you wouldn't want to use this for "Pausing" synchronisation.
As I understand it you want something like the following:
If you turned off iCloud sync while the app is running and then perform some updates, you don't want those changes to be synchronised, but later when you turn iCloud sync back on you want subsequent changes to be synchronised.
I could be wrong but I think you might be out of luck using the standard Core Data/iCloud integration for this.
Depending on your requirements perhaps you could just observe the import notifications and discard any changes - but I can't see how you are not going to end up with all kinds of data inconsistencies that might cause subsequent imports to fail.
// Removes the current Document from iCloud and deletes the local copy.
// There does not appear to be any way of only removing ubiquitous content
// and turning off iCloud sync.
// So before we remove it we make a local copy by appending "_Backup" to the filename
// (we should check this filename does not already exist and add a counter or something)
//
- (void)removeMeFromICloud {
//LOG(#"removeMeFromICloud called");
NSError *error;
NSURL *currentURL = self.fileURL;
NSString *fileType = self.fileType;
NSString *extension = [currentURL pathExtension];
NSString *path = [[currentURL URLByDeletingPathExtension] path];
NSString *backupFilename = [NSString stringWithFormat:#"%#_Backup", path];
NSURL *backupFileURL = [[[NSURL alloc] initFileURLWithPath:backupFilename] URLByAppendingPathExtension:extension];
// We only have one store
NSPersistentStore *currentStore = [self.managedObjectContext.persistentStoreCoordinator.persistentStores objectAtIndex:0];
NSDictionary *currentOptions = currentStore.options;
if ([self buildNewStoreAtURL:backupFileURL type:fileType error:&error]) {
//FLOG(#" reset the moc...");
[self.managedObjectContext reset];
[self.managedObjectContext save:&error];
//Set the document title
//FLOG(#" current displayName is %#", self.displayName);
self.fileURL = backupFileURL;
//set the file extension hidden attribute to YES
NSDictionary* fileAttrs = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:NSFileExtensionHidden];
if(![[NSFileManager defaultManager] setAttributes:fileAttrs
ofItemAtPath:[backupFileURL path]
error:&error])
FLOG(#" unable to set file attributes to hide extension");
// Now remove the old one
bool success = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:currentURL options:currentOptions error:&error];
if (success) {
FLOG(#" removed document from iCloud");
FLOG(#" using backup copy");
}
else {
FLOG(#" error removing document from iCloud");
FLOG(#" error is %#, %#", error, error.userInfo);
}
} else {
FLOG(#" error creating backup before removing from iCloud");
FLOG(#" error is %#, %#", error, error.userInfo);
}
}
// in order to migrate to the cloud we have to build the database from scratch,
// we can't just open it with iCloud parameters because we have to ensure that
// log files get generated in iCloud that will allow the store to be rebuilt from
// scratch by peer devices.
//
- (void)migrateMeToICloud {
//LOG(#"migrateMeToICloud called");
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
NSArray *stores = psc.persistentStores;
NSError *error;
// Now get current URL and add_iCloud to the document name, use this for the new store name
NSURL *currentURL = self.fileURL;
NSString *extension = [self.fileURL pathExtension];
NSString *currentFilePathName = [[currentURL URLByDeletingPathExtension] path];
NSString *uuidString = [[NSUUID UUID] UUIDString];
NSString *newFilePathName = [NSString stringWithFormat:#"%#_UUID_%#",currentFilePathName, uuidString];
// We must make it NSSQLite so get the right extension...
NSURL *newURL = [[[NSURL alloc] initFileURLWithPath:newFilePathName] URLByAppendingPathExtension:extension];
NSDictionary *options = [self storeOptionsForICloud:self.fileURL];
NSString *ubiquityName = [options valueForKey:NSPersistentStoreUbiquitousContentNameKey];
if ([stores count]) {
NSPersistentStore *store = [stores objectAtIndex:0];
if (store) {
//FLOG(#" starting migration...");
NSPersistentStore *newStore = [psc migratePersistentStore:store toURL:newURL options:options withType:NSSQLiteStoreType error:&error];
//FLOG(#" psc is %#", psc);
//FLOG(#" done migrating...");
if (newStore != nil) {
//FLOG(#" new store seems OK...");
// Set custom metadata so we know if it is synced in iCloud next time we open it
[self setiCloudMetaDataForStore:currentURL ofType:NSSQLiteStoreType iCloud:YES ubiquityName:ubiquityName];
}
else {
FLOG(#" error migrating store");
FLOG(#" error is %#, %#", error, error.userInfo);
}
} else {
FLOG(#" store is nil, nothing to migrate!");
}
}
}
// File is NEVER iCloud enabled when we do this.
// Is we do Save As we pnly build a local store wihout iCloud sync.
// User can select iCloud sync once Save As is done
//
- (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 usually would need to create a new UUID for the new document if it is in iCloud.
// But since we create a local copy only we don't need this.
NSMutableDictionary *newOptions = [[NSMutableDictionary alloc] initWithDictionary:currentOptions];
[newOptions setObject:#"DELETE" forKey:#"JOURNAL"];
// Remove any iCloud options (this one includes the unique iCloud UUID
[newOptions removeObjectForKey:NSPersistentStoreUbiquitousContentNameKey];
// Remove Core Data ubiquity metadata
[newOptions setObject:[NSNumber numberWithBool:YES] forKey:NSPersistentStoreRemoveUbiquitousMetadataOption];
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
// Now migrate the store to the new location
NSPersistentStore *newStore = [psc migratePersistentStore:currentStore toURL:newURL options:newOptions withType:typeName error:&myError];
if (newStore) {
// Now set up our custom metadata so we can determine if it has been synced in iCloud next time we open it
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;
}
}

Changing CoreData Model : retro compatibility

I've been hitting this problem with coredata and it's driving me nuts because it should be straight forwards
I'm currently working on the first release of this app, os obviously I keep tweeking the core data model here and there,
However each time change the core data model I need to uninstall the application and reinstall the new version.
This is passable while its just me, but once released I need to be able to change update the app without my users reinstalling.
What am I missing,
Is there some code I need to write to tell core data how to modify the existing persistant data to the new one ?
THanks for your help
Jason
Core data model - migration - adding new attributes/fields to current data model - no RESET of simulator or app required
Steps:
Create Model version from editor - Give it any meaningful name like ModelVersion2
Go to that model version and make changes to your model.
Now go to YourProjectModel.xcdatamodeld and set current version to newly created version.
Add below code to place where you are creating persistent coordinator -
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
and set options value as options for method -
[__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]
In my case, it looks something like this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil) {
return__persistentStoreCoordinator;
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"LGDataModel.sqlite"];
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]);
abort();
}
return__persistentStoreCoordinator;
}
Link: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmInitiating.html#//apple_ref/doc/uid/TP40004399-CH7-SW1
You need to read up on Core Data versioning and migration. Here's a blog post that explains it well:
http://www.timisted.net/blog/archive/core-data-migration/

Resources