CoreData warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass

6.7k views Asked by At

I am suddenly getting a bunch of warnings on iOS12/XCode 9. Why are there multiple managedObjectModels ? The app only has one *.xcdatamodeld file but there are multiple versions within the model.

Is this some new iOS12 Coredata feature and is there something I can do to prevent this warning or should I just ignore it?

2018-09-18 11:45:34.487073+1000 xxxxxxxxx[4422:1419983] [error] warning:     'Stats' (0x2812f1550) from NSManagedObjectModel (0x2806ff480) claims 'Stats'.
CoreData: warning:       'Stats' (0x2812f1550) from NSManagedObjectModel (0x2806ff480) claims 'Stats'.
2018-09-18 11:45:34.487084+1000 xxxxxxxxx[4422:1419983] [error] warning:     'Stats' (0x2812f3bd0) from NSManagedObjectModel (0x2806b18b0) claims 'Stats'.
CoreData: warning:       'Stats' (0x2812f3bd0) from NSManagedObjectModel (0x2806b18b0) claims 'Stats'.
4

There are 4 answers

0
Karrar On

If every time you want to access your persistent container you create an instance of NSPersistentContainer(name:), you will get this warning. I had this issue because I was using two different containers in my storage class(each container for a different managed object model). I solved it by keeping each container in a var property and whenever I change the persistent container, I re-assign it from the property without creating another instance like so:

var mainPersistentContainer: NSPersistentContainer?
var firstContainer : NSPersistentContainer?
var secondContainer: NSPersistentContainer?

And then when you init the container try to assign it to your variable

if firstContainerCondition {
if firstContainer != nil {
mainPersistentContainer = firstContainer
} else {
firstContainer = NSPersistentContainer(name: "firstMangedObjectModel")
}

And the same thing with the second container.

2
André Henrique da Silva On

This happened for me when I was instantiating an new NSManagedObject subclass as follows:

let newItem = Item(context: myContext)

What best worked for me in 2022 was creating an extension as :

extension NSManagedObject {
    
    convenience init(context: NSManagedObjectContext) {
        let name = String(describing: type(of: self))
        let entity = NSEntityDescription.entity(forEntityName: name, in: context)!
        self.init(entity: entity, insertInto: context)
    }
}

With this extension I no longer get the warning, cause I'm initialising the managed object from the entity description. And according to Apple's Documentation the method used in the extension init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) is the designated initializer for NSManagedObject.

"NSManagedObject uses dynamic class generation to support the Objective-C 2 properties feature (see Declared Properties) by automatically creating a subclass of the class appropriate for entity. initWithEntity:insertIntoManagedObjectContext: therefore returns an instance of the appropriate class for entity. The dynamically-generated subclass will be based on the class specified by the entity, so specifying a custom class in your model will supersede the class passed to alloc."

0
Ron Liu On

I just resolved the same error where I had used a computed property for my persistent container. Thus everytime the app accessing persistent container/store, new data model instance is created from the disk.

After I change persistent container to a lazy stored property, the issue's disappeared.

[update]

Currently, I use a separate class for core data stack where a singleton like below is used:

class DataCtrl : NSObject {

    static shared = DateCtrl()
    var container: NSPersistentContainer?
    
    private override init() { 
        container = NSPersistentContainer(name: "dataModelName")
    }

    func loadStore(completionHandler: @escaping () -> ()) {
        self.container?.loadPersisentStores() { desc, err in ...
            completionHandler
        }
    }
}

Then I may comfortably use computed property in tableViewController extension:

var container : persistentContainer { return DateCtrl.shared.container }

of course you need to call the func loadStore in AppDelegate didFinishLaunchingWithOptions block to load persistent store first where using DispatchGroup() in completionHandler to control loading first view controller's data model.

0
Diviyo On

I figured out how to resolve this. You have to create one NSEntityDescription instance in your unit test class and reuse it every time you want to create an object which matches the entity description. In the code below, look at setup, tearDown, and testFetchPerson()

   var container: NSPersistentContainer!

   **var entityDescription: NSEntityDescription! // Insert only one instance into your managed object context * see setup//**

   override func setUp() {
       super.setUp()
       let bundle = Bundle(for: type(of: self))
       let url = bundle.url(forResource: "DivyaPracticeWorkspace", withExtension: "momd")!
       let managedObjectModel = NSManagedObjectModel(contentsOf: url)!
       container = NSPersistentContainer(name: "testContainer", managedObjectModel: managedObjectModel)
       let description = NSPersistentStoreDescription()
       description.type = NSInMemoryStoreType
       container.persistentStoreDescriptions = [description]
       container.loadPersistentStores(completionHandler: { (description, error) in
           if let error = error {
               print("\(error)")
           }
       })

       mockCoreData = CoreDataManager(container: container)
**// Insert only one entity description into the context
       entityDescription  = NSEntityDescription.entity(forEntityName: "Person", in: mockCoreData.mainContext)!**
   }


   override func tearDown() {
       super.tearDown()
       deleteAllObjects()
       mockCoreData = nil
       container = nil
   }

   private func deleteAllObjects() {
       let request: NSFetchRequest<Person> = Person.fetchRequest()
       do {
           let results = try mockCoreData.mainContext.fetch(request)
           for entry in results {
               mockCoreData.mainContext.delete(entry)
           }

           mockCoreData.saveChanges {
               [weak self] in
               guard let this = self else {
                   return
               }

               this.mockCoreData.mainContext.refreshAllObjects()
               this.mockCoreData.mainContext.reset()
               guard let store = this.container.persistentStoreCoordinator.persistentStores.first else {
                   return
               }
               do {
                   try this.container.persistentStoreCoordinator.remove(store)
               } catch let error {
                   print("\(error)")
               }
           }
       } catch let error {
           print("\(error)")
       }
   }

   func testFetchPerson() {
        var fetchSuccess = false
**// Create person entity using the entity description created in setup.
           let personEntity = Person(entity: entityDescription, insertInto: mockCoreData.mainContext)**

           personEntity.name = "Bob"
           personEntity.gender = Int32(0)

           mockCoreData.saveChanges(completion: nil)


           if let personFetched = mockCoreData.fetchPerson(name: "Bob")?.first {
               fetchSuccess = personFetched.gender == Int32(0) && personFetched.name == "Bob"
           }
       XCTAssertTrue(fetchSuccess)
   }