views:

165

answers:

2

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?

A: 

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.

octy
+1  A: 

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.

TechZen
I thought about this kind of problems and therefore I created two NSManagedObjectContexts (one for the main thread and one for the sync thread). Isn't that a valid solution? I also use NSFetchedResultsController for my tableviews.
Roland
Yes, but when you merge the changes made by the contexts, they trigger the need to update to reflect the changes made in the other. The FRC delegate methods let you tell the UI to freeze while those changes are processed. Then you unfreeze the UI and let it show the updates. This process is usually invisible to the user.
TechZen
The app crashes before I merge the changes into the main thread.
Roland
I should add that if you don't freeze a tableview, it may try to redraw itself while its data is in flux. This causes it to receive the wrong number of sections and rows which leads to intermittent crashes.
TechZen
The crash is happening because you are fetching some objects in a relationship while you are simultaneously mutating the set that holds that relationship. It's probably when you are fetching the department for each employee while you are adding employees to the departments relationships. Adding a department to an employee automatically adds the employee to the department. Depending on exactly how you code all this, they might collide.
TechZen