Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data executeFetchRequest throws NSGenericException (Collection was mutated while being enumerated)

I'm developing a iPhone app with Core Data. All user data should be synchronized with our servers. For this purpose I created a subclass of NSOperation witch loads new data from our web service and creates corresponding managed objects. To maintain the relationships between them, every object is transmitted with a remoteID (which is the primary key of the relational server DB).

Let's say there are two managed objects: Department <-->> Employee. The synchronization works as follows:

  1. Load all departments from server. For each department: create a Department object and set its remoteID.

  2. Load all employees from server. For each employee: create Employee object, fetch the related Department (by remoteID) and assign it to the employee.

Fetching a department leads to the following exception:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x69c8a10> was mutated while being enumerated.<CFBasicHash 0x69c8a10 [0x2d6d380]>{type = mutable set, count = 1424, 
entries => <A list of all newly created entities>

*** Call stack at first throw:
0 CoreFoundation  0x02d04919 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x02e525de objc_exception_throw + 47
2 CoreFoundation  0x02d043d9 __NSFastEnumerationMutationHandler + 377
3 CoreData        0x026225d0 -[NSManagedObjectContext executeFetchRequest:error:] + 4400
4 myApp           0x00059de4 +[AppFactory departmentWithRemoteID:inManagedObjectContext:] + 259

The exception isn't thrown every time. Moving the code to the main thread resolves the problem. I have no idea what's wrong. I created a new NSManagedObjectContaxt in the synchronization thread and passed all managed objects by its NSManagedObjectID.

Any thoughts?

like image 657
Roland Avatar asked Jul 19 '10 13:07

Roland


3 Answers

I had the same problem... It was solved because I was using the managedObjectContext that was created on the main thread on a background thread. The solution was to create a different ManagedObjectContext on the background thread, and use the regular persistentStoreCoordinator... it worked fine after that!

like image 180
Marsson Avatar answered Oct 30 '22 22:10

Marsson


The error "someCollection was mutated while being enumerated" is caused by altering a mutable collection i.e. array, dictionary, set etc, while an enumerator is stepping through it. Since you can't enumerate a moving target, this triggers an error.

In this case, the error is most likely caused by trying to enumerate a Department's employees relationship on the main thread e.g. for display in a tableview, while the the background thread is simultaneously adding employees to the relationship.

Did work around this, you have to freeze the UI while you merge the changes from the background thread. For tableviews, a fetched results controller (NSFetchedResultsController) with properly implemented delegate methods in the tableview controller will handle the problem nicely.

The important thing is to send beginUpdates to the tableview before you merge the new data. This will tell the table that it's underlying data structure is being mutated so it won't try to redraw itself. When the merge is complete, send endUpdates to the tableview to cause it to display the new information.

like image 4
TechZen Avatar answered Oct 30 '22 21:10

TechZen


Off the top of my head: Is the "synchronization" thread adding new objects to the Department collection while iterating on it on the main thread?

Usually, this type of exception occurs when you're modifying a collection at the same time you're enumerating it. In a multi-threaded scenario, it might also mean your collection is enumerated and updated concurrently without proper thread synchronization.

like image 1
octy Avatar answered Oct 30 '22 23:10

octy