Multithreading – why does my application crash when modifying the core data relationship in the nsoperation subclass?
background
I have the following object trees:
Name Project Users nil John nil Documents nil Acme Project Acme Project <--- User selects a project Proposal.doc Acme Project 12:32-12:33 Acme Project 13:11-13:33 Acme Project ...thousands more entries here...
>Users can assign a group to an item All descendants are set to this item. > This locks the main thread, so I use nsoperations. > I am using this method approved by apple to see the nsmanagedobjectcontextdidsavenotification and merge it into the main context
problem
My save has failed with the following error:
Pending changes cannot be processed before saving After 100 attempts, the context is still dirty Usually this recursive dirty is caused by a bad validation method, - willsave or notification handler
I tried
I've eliminated all the complexity of my application and done the simplest project I can think of And the error still occurs I tried:
>Set the maximum number of operands on the queue to 1 or 10. > Call refreshobject: mergechanges: at several points in the nsoperation subclass. > Set the merge policy in the managed object context. > Establish and analyze It's empty
My question
How do I set up relationships in nsoperation without my application crashing? Of course, isn't this the limitation of core data? Can it?
code
Download my project: http://synapticmishap.co.uk/CDMTTest1.zip
main controller
@implementation JGMainController -(IBAction)startTest:(id)sender { NSManagedObjectContext *imoc = [[NSApp delegate] managedObjectContext]; JGProject *newProject = [JGProject insertInManagedObjectContext:imoc]; [newProject setProjectName:@"Project"]; [imoc save]; // Make an Operation Queue NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue setMaxConcurrentOperationCount:1]; // Also crashes with a higher number here (unsurprisingly) NSSet *allTrainingGroupsSet = [imoc fetchAllObjectsForEntityName:@"TrainingGroup"]; for(JGTrainingGroup *thisTrainingGroup in allTrainingGroupsSet) { JGMakeRelationship *makeRelationshipOperation = [[JGMakeRelationship alloc] trainGroup:[thisTrainingGroup objectID] withProject:[newProject objectID]]; [queue addOperation:makeRelationshipOperation]; makeRelationshipOperation = nil; } } // Called on app launch. -(void)setupLotsOfTestData { // Sets up 10000 groups and one project } @end
Perform relationship operations
@implementation JGMakeRelationshipOperation -(id)trainGroup:(NSManagedObjectID *)groupObjectID_ withProject:(NSManagedObjectID *)projectObjectID_ { appDelegate = [NSApp delegate]; imoc = [[NSManagedObjectContext alloc] init]; [imoc setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]]; [imoc setUndoManager:nil]; [imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:imoc]; groupObjectID = groupObjectID_; projectObjectID = projectObjectID_; return self; } -(void)main { JGProject *project = (JGProject *)[imoc objectWithID:projectObjectID]; JGTrainingGroup *trainingGroup = (JGTrainingGroup *)[imoc objectWithID:groupObjectID]; [project addGroupsAssignedObject:trainingGroup]; [imoc save]; trainingGroupObjectIDs = nil; projectObjectID = nil; project = nil; trainingGroup = nil; } -(void)mergeChanges:(NSNotification *)notification { NSManagedObjectContext *mainContext = [appDelegate managedObjectContext]; [mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; } -(void)finalize { appDelegate = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; imoc = nil; [super finalize]; } @end @implementation NSManagedObjectContext (JGUtilities) -(BOOL)save { // If there's an save error,I throw an exception } @end
data model
Update 1
I've tried some, and even without merging, I still throw an exception After modifying the relationship, it is sufficient to save the management object context in another thread
I have a shared persistence store coordinator with application representatives I've tried to create a separate nspersistent storecoordinator for threads using the same URL as the data store, but core data complains
I am happy to offer advice on how to make thread coordinators The core data file mentions a method, but I can't see how
Solution
You are crossing a very bad stream (in this case, a thread) in coredata Look at this:
Starttest is called from a button (it is ibaction, assuming the button is clicked) on the main thread > your for loop uses the initialization program trainggroup to create a jgmakerelationship object: withproject: (this should be called init, and super may be called, but this will not cause this problem) > In the main thread, create a new managed object context in the operation. > Now the operation queue calls the operation "main" method from the worker thread (place a breakpoint here and you will see that it is not on the main thread). > Your application thrives because you have accessed the managed object context from a different thread than the thread it created
Solution:
Initializes the managed object context in the main method of the operation