I have a Core Data data model with several model versions (kept to allow migrations). My entities have the codegen setting set to Manual / none, and I have corresponding classes in my codebase where I hook into certain methods like validateForUpdate, awakeFromInsert, etc:

@objc(Book)
class Book: NSManagedObject {
    @NSManaged var title: String

    override func validateForUpdate() throws {
        try super.validateForUpdate()

        try customValidation()
    }
}

During migration of an old store, it is necessary for me to load the old store using the old model version and perform some updates to managed objects. However, the validation from the NSManagedObject class still runs, even though this was only designed to run against the latest version. It may refer to attributes that don't exist in the old version, giving me unrecognized selector errors.

An example of the code I'm using to load a store with the old model is:

// book_5 is the name of the older model version
let model = NSManagedObjectModel(contentsOf: Bundle.main.url(forResource: "books_5", withExtension: "momd")!)!

let container = NSPersistentContainer(name: "TestModel", managedObjectModel: model)
let description = NSPersistentStoreDescription(url: storeLocation)
container.persistentStoreDescriptions = [description]

container.loadPersistentStores { _, _ in
    let entity = model.entitiesByName["Book"]!
    let newBook = NSManagedObject(entity: entity, insertInto: container.viewContext)
    newBook.setValue("Test Book", forKey: "title")
    try! container.viewContext.save()
}

When inserting the object into the context, the awakeFromInsert method is fired, triggering an error because it refers to attributes that only exist in the newest model version.

Is there a way to workaround this? I feel like this should be possible because manipulating source entity objects in a custom entity mapping during a heavyweight migration does not hit the same issue.

1

There are 1 best solutions below

0
cgontijo On

It's hard to know without more context, but from your explanation I understand you want to run the customValidation method only with the new Core Data model version.

Perhaps you can prevent the validation to run depending on what model version you have loaded.

For example, you can create a computed property on NSManagedObjectContext to indicate if the validation should run:

extension NSManagedObjectContext {
    
    var shouldRunCustomValidation: Bool {
        let coordinator = self.persistentStoreCoordinator
        let model = NSManagedObjectModel(contentsOf: Bundle.main.url(forResource: "books_5", withExtension: "momd")!)!
        
        return coordinator?.managedObjectModel != model
    }
}

... and use it in your Book class before running the custom validation:

@objc(Book)
class Book: NSManagedObject {
    @NSManaged var title: String
    
    override func validateForUpdate() throws {
        try super.validateForUpdate()
        
        guard let context = self.managedObjectContext,
              context.shouldRunCustomValidation else { return }
        
        try customValidation()
    }
}