Core Data merge two Managed Object Context
Asked Answered
B

1

23

My Cocoa/Application has a Managed Object Context on the main thread. When I need to update my data my program will:

  1. Start a new thread
  2. Receive new data from a server
  3. Create a new Managed Object Context
  4. Send a notification to the main thread in order to merge the two context

This is the function that receive the notification on the main thread

- (void)loadManagedObjectFromNotification:(NSNotification *)saveNotification
{
    if ([NSThread isMainThread]) {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
    } else {
        [self performSelectorOnMainThread:@selector(loadManagedObjectFromNotification:) withObject:saveNotification waitUntilDone:YES];     
    }
}

I do not receive any error. My problem is the merge result, it actually concatenate Managed Objects from both context.

My Entity are a really simple list of attribute and relationship.

Maybe the merge need some instructions in order to understand when an updated Managed Object IS NOT a new one, but a edited version of the first one. I imagine that somewhere I need to specify a way to univocally identify an Entity, (an attribute for example can act like an ID) and something like a merge policy (if 2 managed object represent the same object, take the one with the lastModificationDate more recent).

I just need to understand how to correctly merge the 2 contexts in order to have a single updated copy for each object.

UPDATE 1

The problem is now clear to me. The 2 context has a big difference: the ObjectID. While the context on the main thread fetched the ManagedObjects with the Persistent Store coordinator, the second thread create those object by fetching a remote URL. Even if the objects have the same contents, they will have 2 different objectID.

My objects had already an unique identificator, I could use setObjectId in order to set this value. (Apple documentation says this is NOT a good idea).

Brach answered 5/8, 2011 at 16:3 Comment(6)
It's obvious that the object IDs are different, since you are creating the objects rather then fetching them from the store and updating them. That was why I said in my answer that you did not need anything else to merge the objects. Anyway, the proper way to handle this situation is the use of managed object IDs. Since managed object IDs are thread safe, you can pass them from your main thread to the other thread, then use existingObjectWithID:error: to retrieve the object associated to a specific ID, update it and save the context. Now the merge will operate as you expect.Atropos
Alternatively, if you do not know in advance what managed object IDs must be passed between the threads, then in your other thread you simply fetch the objects using a predicate to retrieve the ones corresponding to the objects retrieved from the server, then you update them and save the context.Atropos
Correct! The problem were the objectIds. Now every time I do a fetch and I need to update an existing object, I fetch the object from the store and update it.Brach
i'm glad to know that you solved the problem.Atropos
if you rewrite your comment into an answer I will vote you.Brach
Ok, I am going to update the answer, thank you.Atropos
A
33

Here is what you need to do in order to correctly merge the contexts. First, you do not need your own notification. Performing a save operation on a context automatically forwards the following notification to registered observers:

NSManagedObjectContextDidSaveNotification

Therefore, all you need to do is:

1) in your main thread, may be in the viewDidLoad method, register for this notification:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(contextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                            object:nil];

2) implement the contextDidSave: method in your main thread as follows:

- (void)contextDidSave:(NSNotification *)notification
{

    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:); 
    [managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];

}

3) in your dealloc method add the following:

[[NSNotificationCenter defaultCenter] removeObserver:self];

4) create a new context in your other thread using something like the following method:

- (NSManagedObjectContext*)createNewManagedObjectContext
{

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init]; 
    [moc setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
    [moc setUndoManager:nil];
    return [moc autorelease];
}

5) upon receiving the new data, the proper way to handle this situation is the use of managed object IDs. Since managed object IDs are thread safe, you can pass them from your main thread to the other thread, then use existingObjectWithID:error: to retrieve the object associated to a specific ID, update it and save the context. Now the merge will operate as you expect. Alternatively, if you do not know in advance what managed object IDs must be passed between the threads, then in your other thread you simply fetch the objects using a predicate to retrieve the ones corresponding to the objects retrieved from the server, then you update them and save the context.

Atropos answered 5/8, 2011 at 16:56 Comment(3)
This is quite the same of my implementation. My problem is the merge. When I call [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification]; It should merge my ManagedObjects, but it just do a concatenation.Brach
It's difficult to tell what's wrong with your implementation without seeing all of the relevant source code. Are you sure you are saving the data in the other thread's context? Saving is required (because a merge operation compare in-memory objects versus store objects), but you are not mentioning this in your question. About merge policies. certainly you can set one on each context if the default one does not fit your needs. Again, without additional details it's difficult to tell.Atropos
This is a good idea. The problem is if you retrieve the ID it incurs IO. Prefetching doesn't solve this.Instrumentalist

© 2022 - 2024 — McMap. All rights reserved.