Reputation: 17314
So first, this question helped a lot with getting on the right track toward working core data versioning. So I added a new version for my model, and now I'm trying to get the automatic migration working, but I have a problem. I can't remember what my old version looked like! I'm trying to run the app on my phone, but I've been using the simulator for a while and made a few changes to the schema. The version on the phone is from quite a while ago. So each time I try to modify the old version to what I think is on the phone, but I still get the "can't find model for source store" error. I'm guessing it's because I got the old schema wrong.
Is there any way for me to figure out what the schema looks like on the phone? Barring that, how could I just wipe the sqlite store off the phone so I can start over from version 1?
Upvotes: 10
Views: 13285
Reputation: 2604
use core data migrations for updating old database with new schema with losing your data.
Upvotes: 0
Reputation: 4823
I was tearing my hair out over the "Can't find model for source store" error for a whole day. Here is an elaboration of learner2010's answer for googlers:
Your sqlite database's model hash MUST match one of the mom or momd created by your xcdatamodel when you build your app. You can see the hashes in the momd's VersionInfo.plist in the built app's bundle. See below for code to find your database's model hash.
So if you change your xcdatamodel instead of creating a new version under Xcode->Editor->Add Model Version... then your model's hash will be different, and addPersistentStoreWithType won't be able to use your old database, which used the old model. That's what causes the "Can't find model for source store" error.
To make matters worse, the sqlite database is stored in something like "/private/var/mobile/Library/Mobile Documents/YOU_APP_ID/Data.nosync/YOUR_DB.sqlite" and this can stick around even if you remove the app from the device and reinstall it! So you will think there's something wrong with your code, when in reality you just have a stale database that needs to be deleted. Usually this is during debugging so there's no real data in it anyway.
So the proper workflow to allow migrations in the future is to make your model, run your app to build the database, and then create NEW VERSIONS of the model anytime you need to make changes. Everything will "just work" if you keep the changes minor. Then, when you're ready to release your app, select the final model and delete the rest. Then delete your database from "/private/var/mobile/Library/Mobile Documents". Then on future releases, include all the models from previous releases along with your newest model (if it's changed) and users will be able to migrate each time.
Here is my code so far. The important line is:
[fileManager removeItemAtPath:iCloudData error:&error];
But it's only to be used during debugging to delete your old database. Here is the production code in AppDelegate.m:
- (NSManagedObjectModel *)managedObjectModel
{
if (__managedObjectModel != nil)
{
return __managedObjectModel;
}
//NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
//__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//NSArray *testArray = [[NSBundle mainBundle] URLsForResourcesWithExtension:@"momd"subdirectory:nil];
NSString *path = [[NSBundle mainBundle] pathForResource:@"Model" ofType:@"momd"];
if( !path ) path = [[NSBundle mainBundle] pathForResource:@"Model" ofType:@"mom"];
NSURL *modelURL = [NSURL fileURLWithPath:path];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//__managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return __managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if((__persistentStoreCoordinator != nil)) {
return __persistentStoreCoordinator;
}
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator;
// Set up iCloud in another thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ** Note: if you adapt this code for your own use, you MUST change this variable:
NSString *iCloudEnabledAppID = @"RW6RS7HS69.com.zsculpt.soaktest";
// ** Note: if you adapt this code for your own use, you should change this variable:
NSString *dataFileName = @"mydailysoak.sqlite";
// ** Note: For basic usage you shouldn't need to change anything else
NSString *iCloudDataDirectoryName = @"Data.nosync";
NSString *iCloudLogsDirectoryName = @"Logs";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName];
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
if (iCloud) {
NSLog(@"iCloud is working");
NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];
NSLog(@"iCloudEnabledAppID = %@",iCloudEnabledAppID);
NSLog(@"dataFileName = %@", dataFileName);
NSLog(@"iCloudDataDirectoryName = %@", iCloudDataDirectoryName);
NSLog(@"iCloudLogsDirectoryName = %@", iCloudLogsDirectoryName);
NSLog(@"iCloud = %@", iCloud);
NSLog(@"iCloudLogsPath = %@", iCloudLogsPath);
if([fileManager fileExistsAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]] == NO) {
NSError *fileSystemError;
[fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&fileSystemError];
if(fileSystemError != nil) {
NSLog(@"Error creating database directory %@", fileSystemError);
}
}
NSString *iCloudData = [[[iCloud path]
stringByAppendingPathComponent:iCloudDataDirectoryName]
stringByAppendingPathComponent:dataFileName];
NSLog(@"iCloudData = %@", iCloudData);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:iCloudEnabledAppID forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:iCloudLogsPath forKey:NSPersistentStoreUbiquitousContentURLKey];
[psc lock];
NSError *error;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[NSURL fileURLWithPath:iCloudData]
options:options
error:&error];
if( error )
{
NSLog(@"Error adding persistent store %@, %@", error, [error userInfo]);
// comment in this line while debugging if get "Can't find model for source store" error in addPersistentStoreWithType.
// it means the sqlite database doesn't match the new model and needs to be created from scratch.
// this happens if you change the xcdatamodel instead of creating a new one under Xcode->Editor->Add Model Version...
// CoreData can only automatically migrate if there is a new model version (it can't migrate if the model simply changes, because it can't see the difference between the two models).
// be sure to back up the database if needed, because all data will be lost.
//[fileManager removeItemAtPath:iCloudData error:&error];
/*// this is another way to verify the hashes for the database's model to make sure they match one of the entries in the momd directory's VersionInfo.plist
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:[NSURL fileURLWithPath:iCloudData] error:&error];
if( !sourceMetadata )
NSLog(@"sourceMetadata is nil");
else
NSLog(@"sourceMetadata is %@", sourceMetadata);*/
}
[psc unlock];
}
else {
NSLog(@"iCloud is NOT working - using a local store");
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[psc lock];
NSError *error;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localStore
options:options
error:nil];
if( error )
NSLog(@"Error adding persistent store %@, %@", error, [error userInfo]);
[psc unlock];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged" object:self userInfo:nil];
});
});
return __persistentStoreCoordinator;
}
Upvotes: 13
Reputation: 4167
I had the same problem too. I solved it by deleting my old database and building the project again. I am not sure that this is the right way to do. Apparently it is pointing to the wrong database file. In my case, I was creating a simple project using core data and was trying out migration. When I created a new model with an additional column and built my project, there were 2 files - coreData.sqlite and coreData ~.sqlite... So what I feel is that, the wrong database is getting pointed to and hence the error. When I deleted the database and built the project again, it worked perfectly for me.
If you do delete the database, it would be best to save a copy of the database in another location so that you do not lose it.
Upvotes: 0
Reputation: 64428
The error message means it cannot find the .mom
compiled model file for the existing store. Core Data is looking for the exact .mom version that configured the store. The .mom file tells tells Core Data how to map the serialized data in the file into objects. Without that model file, it does not know how to migrate the store to the new model because it doesn't know what data goes with each entity or entity property.
I've only seen this once and IIRC the cause was that the new .mom
file had the exact same name and location as the old one. When the app was updated, the old .mom
file was overwritten.
Try changing the name of the new model file and see if that helps. If not, we will probably need more detail about what you are doing.
Upvotes: 5