Multithreaded Core Data - NSManagedObject invalidated
As the title suggests im working with a Core Data Application which gets filled with objects in different background threads (XML Parsing)
In my background thread I'm doing this
managedContext = [[NSManagedObjectContext alloc] init];
[managedContext setUndoManager:nil];
[managedContext setPersistentStoreCoordinator: [[DataManager sharedManager] persistentStoreCoordinator]];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:managedContext];
NSMutableArray *returnSource = [[self parseDocument:doc] retain];
[managedContext save:&error];
if (error) {
NSLog(@"开发者_Go百科saving error in datafeed");
}
[managedContext reset];
[self performSelectorOnMainThread:@selector(parseCompleteWithSource:) withObject:returnSource waitUntilDone:YES];
The Merge method looks like this:
NSManagedObjectContext *mainContext = [[DataManager sharedManager] managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self];
I think the merge is successful but as i want to display it in an UITableView it always tells me that my objects are invalidated which is to be expected because of
[managedContext reset];
What i want to do is show the Items which are currently in the database, in the background parse the xml and if thats finished i want to update the UITableView with the new / updated objects. How would I do that, can i "update" the objects to the other Context somehow or is the merge not working correctly?
Do I need to define something specific in the Main ObjectContext? I've tried different mergepolicies without any luck.
Hope you can help me, thanks!
I believe your problem is the contents of the returnSource
array. If that is a bunch of NSManagedObject
instances then they will have been created on the background thread by the background thread context.
You call to -[NSManagedObjectContext reset]
will invalidate them, since that is what you explicitly tell the context to do. But that is not the big problem.
You then go on to send the array to a the main thread, passing NSManagedObject
instances over thread borders, and between contexts is a big no-no.
What you need to do is:
- Create an array with the
NSManagedObjectID
s of theNSManagedObject
. - Send the object ID array over thread boundry.
- Recreate an array with
NSManagedObject
s from the managed object IDs on the new thread with it's context.
I have made some Core Data helpers, following the rule of three (the third time you write something, make it general).
Most importantly I have hidden the complexity of managing different managed object contexts for each thread, handling notifications, and all that junk. Instead I have introduced the concept of thread local contexts. Basically lazily created NSManagedObjectContext
instances that automatically registers for updates and cleanup when the current thread exits.
A normal use-case:
NSManagedObjectCotext* context = [NSManagedObjectCotext threadLocalContext];
// Do your stuff!!
NSError* error = nil;
if (![context saveWithFailureOption:NSManagedObjectContextCWSaveFailureOptionThreadDefault
error:&error])
{
// Handle error.
}
The full source code, including a sample app for parsing the news RSS from apple.com and store it in Core Data, is available here: https://github.com/jayway/CWCoreData.
There is no reason in this case to call reset
on the background context because it will disappear anyway with the background thread and you never use it again in any case. You usually use reset
with undo management when you want the context to forget previous steps.
Your major problem here is that your background thread is configured to receive notifications from the managed object context it creates. That is rather pointless.
Instead, you need to register the tableview controller (on the front thread) to receive the NSManagedObjectContextDidSaveNotification
from the context on background thread. That way, when the background context saves, the front/main context will update itself with the new data which will trigger an update of the tableview.
精彩评论