Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoreData warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass

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'.
like image 774
Duncan Groenewald Avatar asked Sep 18 '18 02:09

Duncan Groenewald


3 Answers

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.

like image 67
Ron Liu Avatar answered Oct 22 '22 19:10

Ron Liu


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."

like image 2
André Henrique da Silva Avatar answered Oct 22 '22 18:10

André Henrique da Silva


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)
   }
like image 1
Diviyo Avatar answered Oct 22 '22 19:10

Diviyo