Migrating existing Core Data to iCloud - ios

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!

Related

Migrating from iCloud to Local store crashing app with "Object cannot be nil" at the migration - with the use of Core Data

I have an iOS 7 only app which uses Core Data for storage and I am bringing iCloud, with a switch to the app.
Every aspect of the iCloud integration is working, except the migration from the iCloud Store to the Local store if the user turns off iCloud from within the app.
Through the use of an Exception Breakpoint, the app is crashing with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM replaceObjectAtIndex:withObject:]: object cannot be nil'
This is crashing at the "migratePersistentStore" code.
This is the code that performs that:
- (void)migrateiCloudStoreToTheLocalStoreAfterUserTurnedOffiCloudInSettings
{
// So we can see what's going on, we'll write out the current store URL before the migration
NSURL *storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
NSLog(#"Current Store URL (before iCloud to Local migration): %#", [storeURL description]);
// NSPersistentStore *currentStore = self.persistentStoreCoordinator.persistentStores.lastObject;
NSPersistentStore *currentStore = [[self.persistentStoreCoordinator persistentStores] firstObject];
// We'll create a new URL
NSURL *localURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Envylope.sqlite"];
NSDictionary *localStoreOptions = nil;
localStoreOptions = #{ NSPersistentStoreRemoveUbiquitousMetadataOption : #YES,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
NSError *error = nil;
[self.persistentStoreCoordinator migratePersistentStore:currentStore
toURL:localURL
options:localStoreOptions
withType:NSSQLiteStoreType error:&error];
NSLog(#"Current Store URL (after iCloud to Local migration): %#", [localURL description]);
// We'll write out a NSError here to see if there were any errors during the migration
NSLog(#"Error from iCloud to local migration %#", error);
// We'll just do a quick check to make sure are no errors with this procedure.
NSLog(#"Erros's after all of that %#", error);
NSLog(#"Current Store URL (after everything): %#", [localURL description]);
[self removeCloudObservers];
}
Issue
The app will crash, with the error above, at the migratePersistentStore line above. I cannot figure out what to do to get this working.
The commented out code for the currentStore shows that I've also tried checking for the lastObject, instead of the firstObject, and in both cases, I'm getting the same result.
I don't get any crashes from the local to iCloud because I want to make sure, if the user is using iCloud but then chooses not to, their data should be migrated locally.
This (Migrate Persistant Store Crash) Stack Overflow question seems like it would be a perfect fit, but firstly the answer isn't accepted and there's no confirmation that the code works from the person asking the question and also, that code didn't work for me; it simply removed the data.
Any guidance on this would really be appreciated.
With paying close attention to the link : Migrate Persistant Store Crash, I was able to get this working with the following code:
NSPersistentStoreCoordinator * persistentStoreCoordinator = self.persistentStoreCoordinator;
NSPersistentStore * persistentStore = [[persistentStoreCoordinator persistentStores] firstObject];
if([[NSFileManager defaultManager]fileExistsAtPath:localURL.path])
{
NSLog(#"File exists");
[[NSFileManager defaultManager] removeItemAtPath:localURL.path error:&error];
NSLog(#"Removing error = %#", error);
}
[[NSFileManager defaultManager] copyItemAtPath:persistentStore.URL.path toPath:localURL.path error:&error];
NSLog(#"The copying error = %#", error);
NSPersistentStoreCoordinator * newPersistentStoreCoordinator;
newPersistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
[newPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localURL
options:localStoreOptions
error:&error];
NSLog(#"The adding error = %#", error);
Hopefully this will help someone with the same issue

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.

Core Data Automatic Lightweight Migration failing after moving sqlite file

in my app I've used Core Data Automatic Lightweight Migration fine for multiple versions. About a year ago I implemented iOS File Sharing so I moved the sqlite database to the /Library folder rather than the /Documents directory to keep it hidden. I haven't added another Core Data version since that time.
Now when I try to add another Entity and increase the model version I get the following error (truncated to show just the tail end):
"_NSAutoVacuumLevel" = 2;
}, reason=Can't find model for source store}
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason:
'This NSPersistentStoreCoordinator has no persistent stores.
It cannot perform a save operation.'
I've verified that the .sqlite file exists on disk as expected. I've been troubleshooting for a few days and it seems if I move the sqlite file back to the /Documents directory the migration works OK.
Below is the code for the persistent store coordinator:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self libraryDocumentsDirectory] stringByAppendingPathComponent: #"myappdb.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
//Pass options in for lightweight migration....
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:options error:&error]) {
/*Error for store creation should be handled in here*/
NSLog(#"ERROR IN MIGRATION........%#", error);
}
return persistentStoreCoordinator;
}
- (NSString *)libraryDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
Has anyone encountered this situation before?
Thanks in advance for your help.
"Now when I try to ad another model entity and increase the version number..."
You need to create a new model version before you create a new entity or make changes to existing entities. Make sure your changes were not made in the old model version by mistake.
After creating version and marking it as a current version you have to add an extra value in options which is used to add PersistentStore and then you go( I am not sure about other iOS version but yeah it will definitely work on iOS 7).
-(NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil)
{
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil)
{
return persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ABC.sqlite"];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] ini tWithManagedObjectModel:[self managedObjectModel]];
//Creating Lightweight migration.
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSSQLitePragmasOption: #{#"journal_mode": #"DELETE"}
};
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}

Questions about CoreData and pre-populated models

To pre-populate CoreData in my app upon first launch, I have included a PreModel.sqlite file that was previously created by the app from data that it downloaded from a web service, which includes images.
To populate the model, I do this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSLog(#"creating new store");
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"PreModel.sqlite"];
if(![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSString *sqlitePath = [[NSBundle mainBundle] pathForResource:#"PreModel" ofType:#"sqlite"];
if (sqlitePath) {
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:sqlitePath toPath:[storeURL path] error:&error];
if (error) {
NSLog(#"Error copying sqlite database: %#", [error localizedDescription]);
}
}
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
It seems to work. But I have 2 questions:
If the sqlite file is just a database file and does not actually contain any images, how is the app finding and loading the images on first launch?
Even on subsequent runs of the app I see "creating new store" logged every time. Why is _persistentStoreCoordinator always nil? I am clearly setting it in the code.
It's possible to store images in a database file, usually as binary blobs (which look like instances of NSData in Core Data). If you can provide more info about your model or the code that stores/loads the images, we can be more specific.
"Creating new store" is expected to get logged every time the app is launched in this instance. Even though the SQLite file is persistent on disk, you can't expect data structures in your code to stick around when your app is terminated - you need to create a new persistent store object for your program every time it launches.
Think of it like assigning NSInteger x = 10, then expecting to be able to quit and relaunch your program while maintaining that x has the value 10. That's not how programs work - you'd need to reassign x = 10 before you can expect to read x and get 10 back. The variable _persistentStoreCoordinator works the same way here.

iCloud Core Data Sync Setting

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.

Resources