An error occurring during Core Data persistent store migration in iOS 13 - ios

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.

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:
Select the actual *.xcdatamodel.
Select Editor > Add Model Version.
Provide a version name based on the previous model (like XxxxxxV2.xcdatamodel).
Click on this new version model NewV2.xcdatamodel.
Select this new version as Current on Properties at right hand of IDE.
Make your changes at DDBB.
Run app and will work fine.
I did tests overriding app (with new values) and it was fine.
I hope this may help.

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.

Related

Core Data model version with new entity

I am unable to migrate my project to a new model version, and I have created the smallest project possible to highlight my issue, and hope that someone can show me what's wrong with my approach.
I have the project in GitHub: MigrateApp
I create a new project in Xcode, call it MigrateApp, choose SwiftUI and hook on the checkbox for Core Data. This should also work with UIKit, because I am not doing anything with the UI.
I immediately run the app, and click the plus button in the upper right a couple of times, to populate the Core Data with some items.
Then I stop the app from Xcode, and click the Core Data MigrateApp in Xcode. I click the Editor menu and choose Add Model Version. I choose the name MigrateApp v2. And I set the current model version to the new version, so that the green checkmark shows in MigrateApp v2.
The entities in MigrateApp v2 shows only one entity, called Item. I now want to add a new entity called Subitem, that have a one-to-one relation to Item. I add the Subitem entity, and adds two attributes, date with type Date and text with type String, both non-optional.
Then I add relationship sub to Item, with destination Subitem, and relationship item to Subitem with destination Item, and sets the inverse of both relationships.
Add a new file, choose Mapping Model type, set the source to MigrateApp.xcdatamodel and target data model to MigrateApp v2.xcdatamodel. Give it the name Modelv1Tov2.
I think that I have done everything correct up to now, so the next steps I am not quite sure I do correctly.
I open the Modelv1Tov2 mapping model file in Xcode, and sets the source of the Subitem entity mapping to Item in the Entity Mapping panel. This renames the Subitem to ItemToSubitem.
Clicking the ItemToItem, I see that the relationship mappings show sub. I click on sub, and sets the Key Path in Relationship Mapping to $source.sub, and mapping name to ItemToSubitem.
Likewise with ItemToSubitem, I click item relationship mappings, and set Key Path to $source.item and mapping name to ItemToItem.
The last thing I need to do, is ensure that Xcode migrate the database automatically, and don't try to infer the migration but use the migration mapping. Add the two lines to Persistence.swift in the init, after setting the container property:
container = NSPersistentContainer(name: "MigrateApp")
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "MigrateApp")
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = false
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
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)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
When I now runs the app again from Xcode, it crashes with the message: Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0x600001d1fcf0> valueForUndefinedKey:]: the entity Item is not key value coding-compliant for the key "item".'
What have I done wrong here?
I have searched for answers to this problem, and I found that I do not need to set the shouldMigrateStoreAutomatically = true and shouldInferMappingModelAutomatically = false. If I let Core Data infer the migration, the migration will be successful. So I remade the steps above for a fresh project, and skipped the step where I added the two lines to Persistence.swift, and also skipped the step adding a mapping model, and the migration was successful. I would still want to know why I had the crash when using the mapping model, and will investigate that issue further.

Does tearing coredata stack will solve model update problem?

suppose latest app release version is version no 20. and there are 10 coredata migrations so far till version no 20.
and for example: a user which is using version 3. directly do auto update to version no 20.
i want to delete coredata stack and rebuild it then.
but does it solve that model update issue ? means version 3 uses v3 data model update (xcdatamodel) and version 20 uses v20 data model update(xcdatamodel).
if i delete and rebuild coredata with below code when user autoupdate app from version 3 to 20.
then will it also point to new xcdatamodel version ?
var allstores : Array = self.storeContainer.persistentStoreCoordinator.persistentStores
for store in allstores{
// remove store file from coordinator
do {
try storeContainer.persistentStoreCoordinator.destroyPersistentStore(at: store.url!, ofType: NSSQLiteStoreType, options: nil)
} catch let error {
print("\(error.localizedDescription)")
}
}
and then rebuild..
self.storeContainer.loadPersistentStores(completionHandler: { (nsPersistentStoreDescription, error) in
guard let error = error else {
return
}
fatalError(error.localizedDescription)
})
Yes. I implemented this strategy once I decided to rebuild the database when it was incompatible with older versions. Side node: now I just rely on CoreData to handle the migration.
To test if it works, create an entity with a non-mandatory field and insert a row for that entity without a value for that field. Now in your new data model, make that exact same field mandatory. Now what you will see is an app crash if you don't destroy the persistent store and rebuild it, as you are showing in your code. However, if you rebuild it, it will work.
Note that the user has lost all of his data, but I presume you already know that. I had some code that queried the database for existing data and it copied the local cache from the server.

Fatal Error :Can't open realm

I am working on IOS. I am using realm database in the frontend. It was working fine until I made some changes to the realm model and all files related to it. I just added one field to these files.
Now I am getting an error "Fatal error: Can't open realm" in the following code
fileprivate func getRealm() -> Realm {
// get default configuration realm
do {
return try Realm()
} catch {
Swift.fatalError("Can't open realm") //Fatal Error :Can't open realm
}
}
Can anyone tell what might be the possible causes for this error.
Thanks in advance.
If you made changes to the realm model, you need to increase your schema version and you may or may need to provide a migration block. See the official documentation for details.
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
config.schemaVersion = 1;
// Set the block which will be called automatically when opening a Realm with a
// schema version lower than the one set above
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:config];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];
The above issue is caused due to inconsistency in the Realm database. I changed the structure of the model by adding one field. So, the model was having 10 fields but the realm database in my app was having 9 fields, as the app was built before the changes.
I solved the issue by just reinstalling the app which regenerated the realm database in the phone, thereby having a consistent model and database.
The above workaround might be good if your app is yet to be live. But, if your app is already being used by other users, then they will have to reinstall the app on update, which might result in bad user experience for the user.
Ideally, you should be writing a migration block for the new changes.
Hope, this is clear to all.

Xcode 8 Core Data App Crash After Change of Project Name

I changed the name of my Xcode 8 project how described in this post How do I completely rename an Xcode project (i.e. inclusive of folders)?
Now when running the new version of the app on a device where the old version is installed the app crashes with the following error message:
[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class
(OLD_PROJECTNAME.SomeObject) for key (NS.objects); the class may be
defined in source code or a library that is not linked
So it seems that the old name doesn't fit to the core data stack of the renamed version.
How can I change this name to make the app executable?
EDIT:
This is the core data initialization code inside my AppDelegate.Swift:
// MARK: - Core Data stack
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: "Data")
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)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
It's a production app and the user should not lose any data after the update.
You mentioned in a comment that you have some transformable attributes. This is consistent with your error message, since [NSKeyedUnarchiver decodeObjectForKey:] is part of NSCoding, and since Core Data uses NSCoding with transformable attributes.
This isn't a Core Data problem. You'd get the same error if you used NSCoding on the values of those attributes and wrote the result to a file. In Swift, the full name of a class is something like AppName.ClassName. If AppName doesn't match, NSCoding can't decode the object. You saved objects with a name like OLD_PROJECTNAME.SomeObject, but now your class name is something like NEW_PROJECTNAME.SomeObject. NSCoding can't handle that on its own. You need to help it.
To fix this, you need to tell the archiving system what class to use. You can do that with the NSKeyedUnarchiver class method setClass(_:forClassName:). You might have to experiment a little to get the syntax right but you'll have something like
NSKeyedUnarchiver.setClass(SomeClass.self, forClassName:"OLD_PROJECT_NAME.SomeObject")
You must do this before you attempt to create any objects that use this class so that it will affect how those objects are created. Since you're using Core Data that means before doing anything at all that touches Core Data in any way.

CloudKit: CKFetchRecordChangesOperation, CKServerChangeToken and Delta Download

My question is related to the "Delta Download" thing as it was named in WWDC 2014 Advanced CloudKit.
I'm trying to make syncronization for my Core Data app, which is iPhone only for now (think: there is only one device active). So, basically the app will store user records in the cloud from one same device, for the most cases for now.
I have trouble with understanding custom zone feature which is based on CKFetchRecordChangesOperation aka Delta Download.
As I got it right, we have CKServerChangeToken's to maintain sync operations (I mean download only those records which was added/modified/deleted by another device), as was presented on WWDC.
But, what I can't understand is that we recieve that token only after CKFetchRecordChangesOperation, when we save records to the cloud we don't get new token.
And if we make fetch with the current available token (since it changes only after fetch), we recieve records that was saved from our previous save operation. Basicaly we get save recods that already have on our device. Why? I'm missing something here?
What if we seeding some data to the cloud (from device A), it is justified for situation when device B is fetching the zone records, but what if device A be? Download all the records again?
I found recordChangeTag in the CKRecord, is this a property I can use for resolving conflicts with local objects - fetched objects (same or different version), if so can somebody give me example of how I need to do this: save recordChangeTag to Core Data when save record to CloudKit for the first time or how?
The lack of documentation is such a headache.
I found a time to write an answer for this question. I won't dig into implementation, but I will discuss the concept.
CloudKit provides a way to data synchronisation between your device and the CloudKit server.
What I use to establish synchronisation process in my case between iPhone and server only (again, if you have iPhone + iPad app, the process require more steps.):
I have custom zone in the private cloud database.
I use OperationQueue to establish different asynchronous processes which depend on each other. Some operations have own operation queues.
Steps:
1) Check if my custom zone is exist
1.1) If there is no custom zone
1.2) Create new custom zone. (Optional: add records)
1.3) Refresh zone change token
You can refresh zone change token by: performing
CKFetchRecordChangesOperation,
fetchRecordChangesCompletionBlock returns CKServerChangeToken
save it to UserDefaults (for example) using NSKeyedArchiver). This operation's task is to refresh token and it's performed at the end synchronisation process.
2) If there is custom zone already
2.1) Get changes from zone using previously saved zone change token. (CKFetchRecordChangesOperation)
2.2) Update and delete local records.
2.3) Refresh zone change token.
2.4) Check for local changes (I'm using last cloud sync timestamp to check what records was modified after).
2.5) Upload records to cloud kit database
2.6) Refresh zone change token again.
I highly recommend Nick Harris article series: https://nickharris.wordpress.com/2016/02/09/cloudkit-core-data-nsoperations-introduction/
You'll find there implementation and design concepts. It worth reading. I hope somebody'll find all of this helpful.
As of iOS 13 there is a super helpful method in Core Data called NSPersistentCloudKitContainer. This method will automatically take care of all local caching and syncing with iCloud on private databases. You can set it up by simply changing
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
to
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "ShoeTrack")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
You will have to modify the Core Data Model file in your project and check "Use with CloudKit on each configuration.

Resources