Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

core data can't find model for source store - what did my old store look like?

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?

like image 956
Tesserex Avatar asked Aug 27 '10 15:08

Tesserex


1 Answers

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;   
}
like image 102
Zack Morris Avatar answered Oct 23 '22 14:10

Zack Morris