7

After updating XCode to version 11 I added a new model version to Core Data and in new version I added a new attribute to an Entity. Made the new version active and added the new property to managed object file.

After releasing this version to the users it started to crash with the following message: "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." and "duplicate column name ZNEWCOLUMN". Until now I made a lot of changes to the Core Data model and migration always worked.

This crash appears only on iOS 13!

This is how I load Core Data:

    lazy var managedObjectContext: NSManagedObjectContext = {

        return self.persistentContainer.viewContext
    }()

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "MyModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in

            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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 parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        let description = NSPersistentStoreDescription()
        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true
        container.persistentStoreDescriptions.append(description)
        return container
    }()

Any help would be appreciated.

iOS Dev
  • 4,143
  • 5
  • 30
  • 58
  • To add a new attribute you don't need to add a new model version, simply add the attribute to your model and Core Data will made all the work for you. – Gigi Oct 03 '19 at 08:52
  • @Gigi This may be true, I always was creating a new version when adding an attribute. But the question is: how to safely solve this for the users that already made the app update? – iOS Dev Oct 03 '19 at 09:34
  • It might be worth checking and if I figure this out myself I’ll post, but when I instantiate an `NSPersistentContainer`, I always `.append(description)` before I call `container.loadPersistentStores(:)`. – andrewbuilder Oct 03 '19 at 21:43
  • @andrewbuilder that could make sense, in every example I find in google the description is set before the loading as you mentioned. The only interesting question is: how then migration worked until now? – iOS Dev Oct 04 '19 at 07:55
  • That is an interesting question. Again I’m not certain about this - there are people far more familiar with the detailed operations of Core Data than me - I’d guess the `container` remains in memory following the `lazy` instantiation and the `description` is applied, albeit post loading the store, so any subsequent calls to `persistentContainer` may trigger migration. I’m intrigued and will do some more research - if I find any clues I’ll let you know. – andrewbuilder Oct 04 '19 at 11:09
  • This from the Apple documentation [Using Lightweight Migration](https://developer.apple.com/documentation/coredata/using_lightweight_migration)... > “To perform automatic lightweight migration, Core Data needs to be able to find the source and destination managed object models at runtime.” and “Core Data then analyzes the schema changes to persistent entities and properties, and generates an inferred mapping model.” Perhaps implied, the inferred mapping model needs to be created before the store is loaded. – andrewbuilder Oct 04 '19 at 11:52
  • @andrewbuilder I fixed this, but unfortunately crashes continue to happen. – iOS Dev Oct 05 '19 at 05:39
  • So for clarity, you changed the order of the code so that now the description is applied before you load the `NSPersistentContainer`, you no longer receive the error message, but the app continues to crash? – andrewbuilder Oct 05 '19 at 06:09
  • @andrewbuilder Now the description is applied before the loading of persistent container, but the app still crashes on iOS 13 only with the same logs: "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store" – iOS Dev Oct 05 '19 at 10:05
  • So no change in error. I’d recommend - delete the latest model version from the `.xcdatamodeld` package file and set your project target to the previous model version. Check whether this builds and runs... if it does, repeat the creation of a new model version and add the new entity attribute, then run again. I’ve not done this with a production app, so I’d recommend you complete some thorough testing to see how this affects your deployed product before uploading any further changes to the App Store. – andrewbuilder Oct 05 '19 at 14:05
  • @andrewbuilder I tried this, but it also didn't help. Same crashes (( – iOS Dev Oct 07 '19 at 07:45
  • No reproduction project makes it hard to reproduce – J. Doe Oct 11 '19 at 21:09

2 Answers2

3

The same thing is happening to me, lightweight migration at iOS 12 was right at real device and Simulator but at iOS 13 fail with the next log result:

SQLite error code:1, 'duplicate column name: ZNAME_OF_THE_COLUMN .... Error Domain = NSCocoaErrorDomain Code = 134110 "An error occurred during persistent storage migration."

I load data like @iOS Dev post. I check the xxxx.sqlite database file in the emulator path before and after the migration and there were no columns with those new same names. To know the route of the *.sqlite in emulator you have to put a breakpoint and when it is stopped put in the console po NSHomeDirectory(). Then go to Finder window, tap the keys Control + Command + G and paste the route. Yo can handle it (for example) with DB Browser for SQLite program, it´s free.

After a long search I have seen what has happened to some people but I have not seen any solution.

Mine was:

  1. Select the actual *.xcdatamodel.
  2. Select Editor > Add Model Version.
  3. Provide a version name based on the previous model (like XxxxxxV2.xcdatamodel).
  4. Click on this new version model NewV2.xcdatamodel.
  5. Select this new version as Current on Properties at right hand of IDE.
  6. Make your changes at DDBB.
  7. Run app and will work fine.

I did tests overriding app (with new values) and it was fine.

I hope this may help.

Javi AP
  • 392
  • 3
  • 8
0

If you want to edit the descriptions, you need to do so before you load the stores (and I have no idea what appending a new description would do):

        container.persistentStoreDescriptions.forEach { storeDesc in
            storeDesc.shouldMigrateStoreAutomatically = true
            storeDesc.shouldInferMappingModelAutomatically = true
        }

        container.loadPersistentStores { [unowned self] (storeDesc, error) in
            if let error = error {
                // handle your error, do not fatalError! even a message that something is wrong can be helpful
                return
            }

            // do any additional work on your view context, etc.
        }

If your problem is reproduceable, you should look at the error that's being returned and look for something called ZNEWCOLUMN (though this sounds like a temporary default name?) This nomenclature is the raw column name in the SQL database though, so it's likely the migrator is attempting to add this new column and failing.

Try turning on SQL debugging in your scheme's Arguments:

-com.apple.CoreData.SQLDebug 1

Try logging into the raw SQL database (the above will give you the raw path if you're on the simulator). Try rolling back to the previous data model on a previous OS and then just upgrading to 13.

Sounds like you have some duplicate column somewhere so these are just some ideas to find out where it is.

Procrastin8
  • 4,193
  • 12
  • 25