CoreData warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass
Asked Answered
S

4

10

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'.
Stickpin answered 18/9, 2018 at 2:6 Comment(10)
Well, obviously this would be a bug, either in your code or in Xcode, and not a feature. Apple has probably done little if any tests of last year's Xcode 9 builds running in this year's iOS 12. So, you should first build your project in Xcode 10, and update the results in your question accordingly.Hebe
Good point, I can't use Xcode 10 at the moment because another library I depend on is not compatible with Xcode 10.Stickpin
Same problem using Xcode 10 with and iOS 12 simulatorFemmine
DuncanGroenewald and @Alessandro: Can you confirm that this does not occur when running the same build in iOS 11?Hebe
@JerryKrinock - I never noticed that before and don't have an iOS11 device handy to test at the moment.Stickpin
It doesn’t occour on iOS 11 or iOS 10Femmine
OK, I have a little iOS Swift Core Data app which I just built and tested in Xcode 10. (Apple has been discouraging use of older SDKs for many years, and it appears that this latest version of Xcode gives no choice of SDK.) Testing in the simulator, using both iPhone 8 Plus and iPhone XS Max (which both indicate iOS 12), my app works fine, and in my Xcode Debug console I do not see any such error/warning as in Duncan's question. My app is not document-based, and has one SQLite store in the default Application Support folder.Hebe
So, it would be good for you Duncan and @Femmine to determine what is wrong. The obvious approach is to set symbolic breakpoints in the several NSManagedObjectModel.init() functions, then run and see who is hitting it more than once. Unfortunately, I have been unable to get any such breakpoint to resolve. Either I don't know what I'm doing, or lldb is still not very swift with Swift debugging.Hebe
@JerryKrinock - see what happens if you use a background context as well as the main context. persistentContainer.newBackgroundContext()Stickpin
Seems to be related to loading a model from disk multiple times. Not sure if this is a new behaviour (e.g. apple has a bug in model caching/uniquing/deiniting) or if it's just a new warning. Seems to be fixable by migrating from NSManagedObject.init(context:) to init(entity:insertInto:), because the code will look in a specific model. I'm not doing that though. The behavior is limited to my tests (I don't load models multiple time in the app), and there are no other problems, so I'll just ignore it for now. YMMV.Shibboleth
K
8

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.

Khajeh answered 22/10, 2018 at 4:25 Comment(0)
O
6

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

Octan answered 8/5, 2022 at 12:44 Comment(2)
Your answer could be improved by offering an explanation for why this may solve the OP's problem.Caliban
Hey @lmonninger, I edited the answer. I hope it helps you understanding it better. There's a link to Apple's doc which might be even more helpful.Writhen
S
1

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)
   }
Strategist answered 23/5, 2019 at 5:44 Comment(0)
A
0

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.

Adrianadriana answered 20/10, 2020 at 16:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.