Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple NSEntityDescriptions claim the NSManagedObject subclass ... #275

Closed
UberJason opened this issue Jul 26, 2018 · 18 comments
Closed

Comments

@UberJason
Copy link
Contributor

UberJason commented Jul 26, 2018

Hi Drew,

In Xcode 10 I'm getting this warning in the console when I save new objects to Core Data. (Context: I have an NSManagedObject subclass called CDEvent which I'm creating and saving in this case, and my Core Data stack lives in a shared framework called FeedingKit.)

2018-07-26 16:34:30.268969-0400 Feeding[2936:3350281] [error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'FeedingKit.CDEvent' so +entity is unable to disambiguate.
CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'FeedingKit.CDEvent' so +entity is unable to disambiguate.
2018-07-26 16:34:30.269522-0400 Feeding[2936:3350281] [error] warning:  	 'CDEvent' (0x7ffcb082c570) from NSManagedObjectModel (0x7ffcae58a4c0) claims 'FeedingKit.CDEvent'.
CoreData: warning:  	 'CDEvent' (0x7ffcb082c570) from NSManagedObjectModel (0x7ffcae58a4c0) claims 'FeedingKit.CDEvent'.
2018-07-26 16:34:30.269984-0400 Feeding[2936:3350281] [error] warning:  	 'CDEvent' (0x7ffcb0c09380) from NSManagedObjectModel (0x7ffcb0c06530) claims 'FeedingKit.CDEvent'.
CoreData: warning:  	 'CDEvent' (0x7ffcb0c09380) from NSManagedObjectModel (0x7ffcb0c06530) claims 'FeedingKit.CDEvent'.

I investigated a little bit using the memory graph debugger and noticed that there are three NSManagedObjectModels while my app is running. One of them comes from my own Core Data stack, and two of them come from Ensembles (CDEPersistentStoreEnsemble has a managedObjectModel, and CDEEventStore has a managedObjectContext that points to a persistentStoreEnsemble which points to a managedObjectModel). The two managed object models that the warnings reference correspond to the one from my own Core Data stack, and the CDEPersistentStoreEnsemble's directly-owned managedObjectModel.

So far, I haven't noticed this warning actually causing any problems - my app and Ensembles work as desired. The warning is disconcerting, though, and I'm not sure if you're already aware of it and/or have an easy solution to fix or suppress it. Any ideas?

@drewmccormack
Copy link
Owner

drewmccormack commented Jul 27, 2018 via email

@UberJason
Copy link
Contributor Author

@drewmccormack I'm creating the model object the same way Ensembles is, I believe - by using init(contentsOfURL:).

I'm curious about your comment about Swift modules. How do you think that could affect things? In my xcdatamodeld file, I have the module for my Core Data object defined as Current Product Module. Really, this is a tricky one for me to debug because I have very little insight into how Core Data works under the hood, including the relationship between NSEntityDescription and NSManagedObjectModel.

@UberJason
Copy link
Contributor Author

UberJason commented Jul 29, 2018

Actually, your comment about asking about the model object helped me to realize I need to be researching NSManagedObjectModel and NSEntityDescription, and I realized after reading up on the documentation about those two classes what the problem was. I was creating my managed objects using the convenience initializer init(context:), which probably calls into the designated initializer init(entity:insertInto:).

If I create an entity description myself and use it to call the designated initializer, the warning is no longer generated. I guess whatever entity description they try to use in that convenience method was getting confused by the multiple managed object models. Just leaving this explanation here in case anyone else stumbles into this warning and searches the Ensembles issues for something like it.

Anyway, resolved - thank you for your comment, which was helpful!

@drewmccormack
Copy link
Owner

drewmccormack commented Jul 29, 2018 via email

@shaps80
Copy link

shaps80 commented Jul 29, 2018

I had the same issue recently so I made a simple extension on NSManagedObject to 'override' the default behaviour.

import CoreData

public 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)
    }

}

Posting here in case anyone else comes across this. This way you don't need to update any of the call-sites. 👍

FYI this approach assumes your class names match the entity name exactly.

@ksoftllc
Copy link

ksoftllc commented Oct 9, 2018

Just a comment to @shaps80 that it is probably best not to override the preferred initializer and instead provide a new convenience initializer, i.e. convenience init(using context: NSManagedObjectContext) that does the same as what you describe.

@shaps80
Copy link

shaps80 commented Oct 11, 2018

@ksoftllc fair call 😬

@ksoftllc
Copy link

@shaps80 Your suggestion has saved me twice! Great call.

@pascalfribi
Copy link

Hi Drew,

I stumbled across this issue as well, and the problem really is, that the managedObjectModel should not be loaded more than one time. I have these strange crashes as well when using NSManagedObject.init(entity:insertIntoContext:). In some classes it works, in others it does not.

And it does only crash, when I have ensembles enabled. When it is disabled then it never crashes.

So I experimentally just changed the API to provide the NSManagedObjectModel instead of the URL to the model, so that Ensembles does not need to load it (again). After that my code just worked.

I have found multiple entries in Stack Overflow that talk about this problem.

So the real fix is, that the API of Ensembles should be changed to using the already loaded managed object model. This would permanently fix this issue.

What do you think?

@drewmccormack
Copy link
Owner

drewmccormack commented Apr 11, 2021 via email

@pascalfribi
Copy link

pascalfribi commented Apr 11, 2021 via email

@drewmccormack
Copy link
Owner

drewmccormack commented Apr 11, 2021 via email

@pascalfribi
Copy link

pascalfribi commented Apr 11, 2021 via email

@drewmccormack
Copy link
Owner

drewmccormack commented Apr 11, 2021 via email

@pascalfribi
Copy link

pascalfribi commented Apr 11, 2021 via email

@yunsanch
Copy link

yunsanch commented Apr 26, 2023

hello im getting this same error,
would you anyone be able to sugggest what to change in my code so that error goes again properly?
`
class TailorPersistentController: ObservableObject {

var persistentContainer = NSPersistentContainer(name: "Model")
private var clientFetchRequest = CustomerCD.fetchRequest()
private var measurestFetchRequest = MeasurementsCD.fetchRequest()
private var jobsEventFetchRequest = JobsDueCD.fetchRequest()

@Published var editTask: JobsDueCD?
@Published var jobsEventDue: JobsEvents?

init() {
    persistentContainer.loadPersistentStores { storeDescription, error in
        if let error = error {
            print("error = \(error)")
        }
    }
    
}

func saveCustomerToCoreData(customer: CustomersMeasurementsAndDetails) -> UUID {
    let managedObjectContext = persistentContainer.viewContext

    let id = UUID()
    let customerCD = CustomerCD(context: managedObjectContext)
    customerCD.fullName = customer.clientInfo.fullName
    customerCD.gender =  customer.clientInfo.gender?.rawValue
    customerCD.id = id
    customerCD.phone = customer.clientInfo.phone
    customerCD.comments = customer.clientInfo.comments

    
    // 5. save changes
    try? managedObjectContext.save()
    
    return id  
}
//save pants measurements
func savePantsMeasurementsToCoreData(customerMeasurements: CustomersMeasurementsAndDetails.PantsMeasuresDetails,
                                customerId: UUID) {
    
    let managedObjectContext = persistentContainer.viewContext
    
    //MARK: done to be able to save the pants to the existing customer
    let findClientFetchRequest = CustomerCD.fetchRequest()
    let searchPredicate = NSPredicate(
        format: "id == %@", customerId as CVarArg
    )

    findClientFetchRequest.predicate = searchPredicate

    let clientsCDList = try? managedObjectContext.fetch(findClientFetchRequest)

    guard let clientsCDList = clientsCDList else {
        return
    }

    let customerCD = clientsCDList.first
    
    let measurementsCD = MeasurementsCD(context: managedObjectContext)
    measurementsCD.clothType = ClothType.pants.rawValue
    measurementsCD.customerID = customerId
    measurementsCD.forCustomer = customerCD
    measurementsCD.id = UUID()
    measurementsCD.trouserRoll = customerMeasurements.trouserRoll
    measurementsCD.knee = customerMeasurements.knee
    measurementsCD.flyFrontKnuckle = customerMeasurements.flyFrontKnuckle
    measurementsCD.hips = customerMeasurements.hips
    measurementsCD.waist = customerMeasurements.waist
    measurementsCD.length = customerMeasurements.length
    measurementsCD.rearKnuckleLength = customerMeasurements.rearKnuckleLength
    measurementsCD.otherPants = customerMeasurements.other
    // 5. save changes
    try? managedObjectContext.save()
}



//save job due events
func saveJobEFormToCoreData(jobsEventDue: JobsEvents
                                )-> UUID {
    
    let managedObjectContext = persistentContainer.viewContext
    

    let jodsDueCD = JobsDueCD(context: managedObjectContext)
    let id = UUID()
    
    jodsDueCD.clothingType = jobsEventDue.jobsEventType.rawValue
    jodsDueCD.isCompleted = jobsEventDue.isComplete
    jodsDueCD.date = jobsEventDue.datetime
    jodsDueCD.customerName = jobsEventDue.customerName
    jodsDueCD.clothingType = jobsEventDue.taskType
    jodsDueCD.pieacesClothingtoMake = jobsEventDue.numPieacesOfClothingDue
    jodsDueCD.numPants = jobsEventDue.numPants
    jodsDueCD.numJackets = jobsEventDue.numJacket
    jodsDueCD.numDress = jobsEventDue.numDress
    jodsDueCD.numShorts = jobsEventDue.numShorts
    jodsDueCD.numSkirts = jobsEventDue.numSkirts
    jodsDueCD.amountDue = jobsEventDue.totalDue
    jodsDueCD.amountPaid = jobsEventDue.amountPaid
    jodsDueCD.notes = jobsEventDue.note
    
    // 5. save changes
    try? managedObjectContext.save()
    
    return id
}


func fetchClientsFromCoreData() -> [CustomersMeasurementsAndDetails] {
    
    let clientNameSortDescriptor = NSSortDescriptor(key: "fullName",
                                                       ascending: true)
    
    clientFetchRequest.sortDescriptors = [clientNameSortDescriptor]
    let clientsCDList = try? persistentContainer.viewContext.fetch(clientFetchRequest)
    var convertedClients: [CustomersMeasurementsAndDetails] = []
    
    guard let clientsCDList = clientsCDList else {
        return []
    }
    


    

    for clientCD in clientsCDList {

      
        let client = CustomersMeasurementsAndDetails(id: clientCD.id?.uuidString ?? "", clientInfo: CustomersMeasurementsAndDetails.ClientInfo(fullName: clientCD.fullName ?? "", phone: clientCD.phone ?? "", comments: clientCD.comments ?? "")
                                                         , pantsMeasurment: CustomersMeasurementsAndDetails.PantsMeasuresDetails(id:  "", clothType: "", waist: "", hips: "", flyFrontKnuckle:  "", knee: "" , trouserRoll: "", length: "" , rearKnuckleLength:  "", other: "" )
        )
        convertedClients.append(client)

        
    }
    print("checking whats inside of this \(convertedClients)")
    
    return convertedClients

    }

//fetch events
func fetchEventsFromCoreData(currentTab: String) -> [JobsEvents] {
    // MARK: Predicate to Filter current date Tasks
    let calendar = Calendar.current
    var predicate: NSPredicate!
    if currentTab == "Today"{
        let today = calendar.startOfDay(for: Date())
        let tommorow = calendar.date(byAdding: .day, value: 1, to: today)!
        
        // Filter Key
        let filterKey = "date"
        
        // This will fetch task between today and tommorow which is 24 HRS
        // 0-false, 1-true
        predicate = NSPredicate(format: "\(filterKey) >= %@ AND \(filterKey) < %@ AND isCompleted == %i", argumentArray: [today,tommorow,0])
    }else if currentTab == "Upcoming"{
        let today = calendar.startOfDay(for: calendar.date(byAdding: .day, value: 1, to: Date())!)
        let tommorow = Date.distantFuture
        
        // Filter Key
        let filterKey = "date"
        
        // This will fetch task between today and tommorow which is 24 HRS
        // 0-false, 1-true
        
        predicate = NSPredicate(format: "\(filterKey) >= %@ AND \(filterKey) < %@ AND isCompleted == %i", argumentArray: [today,tommorow,0])
    }else if currentTab == "Delayed"{
        let today = calendar.startOfDay(for: Date())
        let past = Date.distantPast
        
        // Filter Key
        let filterKey = "date"
        
        // This will fetch task between today and tommorow which is 24 HRS
        // 0-false, 1-true
        predicate = NSPredicate(format: "\(filterKey) >= %@ AND \(filterKey) < %@ AND isCompleted == %i", argumentArray: [past,today,0])
    }
    else{
        // 0-false, 1-true
        predicate = NSPredicate(format: "isCompleted == %i", argumentArray: [1])
    }

    jobsEventFetchRequest.sortDescriptors = [.init(keyPath: \JobsDueCD.date, ascending: false)]
    
    
    
    let jobseventsCDList = try? persistentContainer.viewContext.fetch(jobsEventFetchRequest)
    var convertedjobevents: [JobsEvents] = []
    
    guard let jobseventsCDList = jobseventsCDList else {
        return []
    }

    for eventsCD in jobseventsCDList {

        let events =
        
        JobsEvents(amountPaid: eventsCD.amountPaid ?? "",
                   totalDue: eventsCD.amountDue ?? "",
                   datetime: eventsCD.date ?? Date(),
                   note: eventsCD.notes ?? "",
                   numPieacesOfClothingDue: eventsCD.pieacesClothingtoMake ?? "",
                   numPants: eventsCD.numPants ?? "",
                   numjacket: eventsCD.numJackets ?? "",
                   numShorts: eventsCD.numShorts ?? "",
                   numskirts: eventsCD.numSkirts ?? "",
                   numDress: eventsCD.numDress ?? "",
                   isComplete: eventsCD.isCompleted,
                   taskType: eventsCD.clothingType ?? "",
                   customerName: eventsCD.customerName ?? "",
                   clothToMake: eventsCD.clothingToMake ?? ""
        )
        
        convertedjobevents.append(events)
    }
    
    
    return convertedjobevents

    }

}
`

@drewmccormack
Copy link
Owner

I'm not that familiar with NSPersistentContainer, but my guess is that it is just using the default models, merging them all into one. With Ensembles, it is necessary that you keep the models apart, because Ensembles has its own Core Data models.

There should be a way with the persistent container to setup your own model, using the URL of your momd file, rather than letting the container look for all models. Take a look and google around for how you can setup the model for the container. (ChatGPT may even be able to write you code for this.)

@drewmccormack
Copy link
Owner

Here is code from ChatGPT. Don't know if it is correct, but probably is reasonable...

Yes, I can certainly help you with that! Here's an example Swift code that sets up an NSPersistentContainer for Core Data using a specific model file:

import CoreData

// Specify the name of your Core Data model file (without the file extension)
let modelName = "MyCoreDataModel"

// Get the URL of the specified model file in your app bundle
guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd") else {
    fatalError("Failed to find model file with name: \(modelName).")
}

// Initialize an NSManagedObjectModel instance with the specified model file
guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
    fatalError("Failed to initialize managed object model with URL: \(modelURL).")
}

// Initialize an NSPersistentContainer with the specified managed object model
let persistentContainer = NSPersistentContainer(name: modelName, managedObjectModel: managedObjectModel)

// Load the persistent store for the container
persistentContainer.loadPersistentStores { storeDescription, error in
    if let error = error as NSError? {
        fatalError("Failed to load persistent store: \(error), \(error.userInfo)")
    }
}

In this example, you first specify the name of your Core Data model file (without the file extension) in a variable called modelName. Then, you get the URL of the specified model file in your app bundle using the Bundle.main.url(forResource:withExtension:) method.

Next, you initialize an NSManagedObjectModel instance with the specified model file URL using the NSManagedObjectModel(contentsOf:) method.

Then, you initialize an NSPersistentContainer instance with the specified managed object model using the NSPersistentContainer(name:managedObjectModel:) method.

Finally, you load the persistent store for the container using the loadPersistentStores(completionHandler:) method, and handle any errors that may occur during the loading process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants