Core Data, iCloud and two persistence stores fail - ios

I have had an existing data model with a single persistent store, and all is good.
Now, following the WWDC 2012 video "Using iCloud with Core Data" (#227), I've defined two configurations in my model, "Cloud" and "Local" while keeping the original "Default". I've split my entities between "Cloud" and "Local". In my code, I add just the two corresponding persistent stores:
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES,
NSInferMappingModelAutomaticallyOption: #YES};
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Cloud"
URL:[self cloudPersistentStoreURL]
options:options
error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Local"
URL:[self localPersistentStoreURL]
options:options
error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
These are added just fine. Later, when running the app and I try to save some initial trial data as normal, the following exception is thrown:
NSUnderlyingException=Can't resolve how to assign objects to stores;
some objects may have been assigned to stores; use [[managedObject
objectID] persistentStore] to find out what is going where now; use
[managedObjectContext assignObject:toStore:] to straighten things out
I've Googled pieces of this with no hits, and Apple's troubleshooting Core Data document doesn't appear to discuss this. I have no clue what can be causing this or where to look. Any thoughts?

Ensure that you have no relationships crossing from one store into another. Related entities must be located inside the same configuration.
For example you have entity Book configured to be stored in Cloud and Author to be stored into Local. Both are related.
When you now assign an Author to a Book and save, then CoreData cannot handle the relationship and will raise the error you are seeing.

Related

Apple rejected - can not reproduce crash

Apple rejected my app with a crash log indicating that they have tested it on an iPhone 6 with iOS 8.0.2. I tested my app with the same iPhone 6 and also on 8.0.2, it works perfect. I tried it with testflight via iTunesConnect, assuming, that this is exactly the same away as Apple does their testing - obviously not!
Does anyone have an idea, how I can solve that issue (already got in contact with the review team, but they couldn't help, as they say they are not technical support and just test the apps.
The problem I have is (from my point of view anyway strange): It crashes when it add the persistent store:
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately. abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
It obviously crashes because I have the abort()statement still in there, but I'm not sure what to do instead (i.e to handle the problem - just delete and try to add the store again?)
As it works perfect on my iPhone, I assume, that it is not one of the problems listed in the source code (like directory, model etc. am I right?
I'm struggling with that now for several days, any help or idea is more than appreciated!!
Common reason for this to happen is when there is already a file at storeURL and it does not conform to model that you specify.
As a fix for that here is what you can do
1) Enable lightweight migration
2) Delete existing file and try again
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES,
NSInferMappingModelAutomaticallyOption: #YES
};
BOOL successOfAdding = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error] != nil;
if (successOfAdding == NO)
{
// Check if the database is there.
// If it is there, it most likely means that model has changed significantly.
if ([[NSFileManager defaultManager] fileExistsAtPath:storeURL.path])
{
// Delete the database
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
// Trying to add a database to the coordinator again
successOfAdding = [_persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error] != nil;
if (successOfAdding == NO)
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
And about rejection - do you have previous versions of your app?
Also, where are you attempting to store your database? May be there is no access to that path?

Looking to identify where to disable backup on CoreData managed persistent store

I have located the required code necessary to apply the extended attribute to the DB file, however I am having an issue identifying exactly when to apply the attribute. The information I have located indicates that attempting to set the attribute at a point before the file exists will ensure that the setting will not take effect until the app restarts (and hits the code again), however I am not sure whether marker can be set while the DB is open, nor where, specifically the file is guaranteed to be opened in the persistentStoreCoordinator. I'm using the standard method structure generated for a CoreData application (originally included in the AppDelegate, just moved to a separate class to make supporting multiple db's easier):
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
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;
}
I know I'm going to have to call addSkipBackupAttributeToItemAtURL (from Apple sample code) on _storeURL, but am I correct in thinking I will also have to identify the extra files created by default on an SQL db (or turn off WAL on the db). Furthermore, I'm not certain when I will need to call this to ensure that the file is correctly marked on the first run. Primarily, am I guaranteed that the store files will all exist just before the return in this method, or are any of them not created until they are used, or after the managed object context is created, etc?

Updating Core Data +iCloud model with the next App version

I have to work on the next version of an Application that uses Core Data and iCloud.
iCloud has been activated in the current version using the most "modern" way:
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"data_cfg"
URL:storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:#"theStore"}
error:&error])
Now I need to update the store structure adding new entities, relationship and so on...
I'm totally stuck because I don't know which is the better way to update a model knowing that iCloud is synchronised with users data.
Which is the best way to perform an update like that? What should I be aware of and what I should pay attention for the most? How to migrate the current data?
Updating a model when using iCloud is the same as when not using iCloud-- with the sole exception that your changes must be ones that work with automatic lightweight migration. Apple's documentation describes the requirements for this kind of migration in detail. The basic steps are:
Create a new version of the data model, and make this version "current". (You must keep the old model around, so now you'll have two, but only one is current).
Make your model changes in the new version.
Add NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to your options dictionary in the code above, using #YES as the value for both.
Now when you launch the app, Core Data will compare the old and new versions of the model and (assuming your changes work for automatic lightweight migration) modify the persistent store to use the new version.
Keep in mind that iCloud syncing only works between devices that use the same version of the data model. If a user upgrades your app on one device but not another that they use, syncing will stop until they upgrade the app on other device as well.
iCloud Backup doesn't matter, by the way here is solution for installing new sqlite model.
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If your'e data can't be recreated (synced from server), than You need to setup migration stack
here is link to Apple's guide https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/CoreDataVersioning/CoreDataVersioning.pdf

Core Data - how to handle _persistentStoreCoordinator errors on first release?

I'm just putting the finishing touches to my app for its first release. I've successfully implemented Core Data but not sure what to do about the persistentStoreCoordinator method that says "Replace this implementation with code to handle the error appropriately."
If I change the model for an update I will look into migration but for now what should I do here?
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Data.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
The failure of a NSPersistentStore being added to a NSPersistentStoreCoordinator is a developer level error. This should never fail in production if you have done adequate testing. Therefore I always put my own version of the following:
NSLog(#"Failed to load persistent store: %#\n%#", [error localizedDescription], [error userInfo]);
abort(); //My personal version throws a NSException
This is a hard error and should be a hard error 99% of the time. This absolutely should crash in development so that you can know if it happens and you are forced to solve it. Since it is a developer level error you can leave this code in place since it should never happen in production and if it does, you want a crash report sent to Apple.

Migrate persistentstore to iCloud

with this code, I can migrate persitentstore (core data) to iCloud:
NSPersistentStore *persistentStore = [[persistentStoreCoordinator persistentStores]objectAtIndex:0];
if(![persistentStoreCoordinator migratePersistentStore:persistentStore toURL:[NSURL fileURLWithPath:iCloudData] options:options withType:NSSQLiteStoreType error:&error])
{
NSLog(#"errore migrate %#", error);
}
But I want to migrate only if the Ubiquity Container of iCloud is empty and the persistent store there isn't.
This is identical to the problem described in:
How to seed initial data to Core Data + iCloud?
You can't reliably detect the initial state of the container, and therefore have to allow for the possibility of duplicate records after your migration.

Resources