NSManagedObject values are correct, then incorrect when merging changes from parent to child NSManagedObjectContext

不问归期 提交于 2019-12-24 13:16:06

问题


I’m having a Core Data issue while using 2 NSManagedObjectContext, running on different threads, and migrating changes from the parent to the child. In essence, I’m able to pull the changes from parent to child, but immediately after doing so, the changes are lost.

The app I’m building is a test for syncing a model between multiple devices and a server.

The context that holds the objects the user interacts with is on the main thread and is configured as a child of the sync context and is created like this (error checks omitted)

NSManagedObjectContext *parentMOC = self.syncManagedObjectContext;
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[_managedObjectContext performBlockAndWait:^() {
    [_managedObjectContext setParentContext:parentMOC];            
}];

The syncManagedObjectContext is the parent context and is where the syncManager performs the sync with the server. It gathers up objects modified by the user, sends the changes to the server and merges changes received back. The syncManagedObjectContext also sends its data to the PersistentStoreCoordinator to be stored in SQLite.The context runs on a background “thread” so that syncing and storing does not block the main thread. Here’s how it is created:

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_syncManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_syncManagedObjectContext performBlockAndWait:^(){
    [_syncManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];

Sync Logic Flow

The syncing is kicked off when the syncManager handles the NSManagedObjectContextObjectsDidChangeNotification from the main context. Here is a rough flow of what happens:

  1. syncManager handles NSManagedObjectContextObjectsDidChangeNotification which lets it know that objects have been changed in the main thread context. It calls save on the main context which saves the changes to syncMOC.
  2. When the syncManager receives NSManagedObjectContextDidSaveNotification to indicate the save has been completed, it gathers up the newly changed objects from the sync context and sends the changes to the server. It then does a save on the sync MOC which sends the data to SQLite. Note that each object has a uuid field which I create as a portable id - not be confused with Core Data’s objectID, as well as a lastSynced timestamp that is provided by the server.
  3. The server responds back with updated timestamps for the objects sent, as well as any other changes that have happened. In the simplest case that illustrates the issue, what is received is a set of records that consists of the uuid and the updated lastSynced time for the objects that the syncManager just sent.
  4. For each update, the syncManager updates the object in the syncContext and stores the NSManagedObject objectID for the object (not the uuid) in an array.
  5. The syncManager then does a save on the on the sync MOC to write the data to disk and posts a message to provide the main MOC with the array of objectID’s for updated objects. At this point, if I do a fetch for all Entities in the syncMOC and dump them to the log, they all have the correct values. Further, if I look at the SQLite database on disk, it too has the correct values.
  6. Here’s abbreviated code (some error checking and non-essential stuff removed) for how the updates are merged in on the main thread, with comments as to what’s happening: (Note: I’ve been careful in the code to use performBlock and it appears from tracing in the debugger that everything is happening on the correct thread.)
-(void)syncUpdatedObjects: (NSNotification *)notification
 {
    NSDictionary *userInfo = notification.userInfo;
    NSArray *updates = [userInfo objectForKey:@"updates"];

    NSManagedObjectContext *ctx = self.managedObjectContext;

    [ctx performBlock:^() {
        NSError *error = nil;

        for (NSManagedObjectID *objID in updates) {
            NSManagedObject *o = [ctx existingObjectWithID:objID error:&error];
            // if I print out o or inspect in the debugger, it has the correct, updated values.  


            if (o) {
                [ctx refreshObject:o mergeChanges:YES];
                // refreshObject causes o to be replaced by a fault, though the NSLog statement will pull it back.
                // NOTE: I’ve used mergeChanges:NO and it doesn’t matter

                NSLog(@"uuid=%@, lastSynced = %@", [o valueForKey:@"uuid”], [o valueForKey:@"lastSynced"]);
                // Output: uuid=B689F28F-60DA-4E78-9841-1B932204C882, lastSynced = 2014-01-15 05:36:21 +0000
                // This is correct. The object has been updated with the lastSynced value from the server.               

           }

        }
        NSFetchRequest *request = [[NSFetchRequest alloc] init];

        NSEntityDescription *entity = [NSEntityDescription entityForName:@“MyItem"
                                                  inManagedObjectContext:ctx];
        request.entity = entity;
        NSArray *results = [ctx executeFetchRequest:request error:&error];
        for (MyItem *item in results)
            NSLog(@"Item uuid %@ lastSynced %@ ", item.uuid, item.lastSynced);
        // Output:  uuid B689F28F-60DA-4E78-9841-1B932204C882 lastSynced 1970-01-01 00:00:00 +0000
        // Now the objects have incorrect values!
    }];

 }

In case you missed it, the issue is there in the comments after the NSLog statements. The object initially has the correct values from the parent context, but then they become incorrect. Look at the timestamp, specifically.

Does anyone have any idea why this would happen? I should note that the business of doing the fetch at the end was part of debugging. I was noticing that the NSManagedObjects being held on to in the program did not have the correct values, even though I was seeing that things were updated correctly in the above code and through uniquing, they should be updated too. I thought that what might be happening is that I was creating “extra” objects with the correct values while the old ones were still around. However, the fetch showed that the right objects and only the right ones were around, only with bad values.

One more thing, if I do the same fetch in the parent context after this function runs, it shows the correct values as does SQLite.

Any help is much appreciated!


回答1:


I finally found the answer to this issue and hope it might help others.

What I noticed at some point is that the object ID coming back to the main context had incorrect Core Data ID - they should have been permanent, but were not. And in fact, during the merge, I realized that the ID for a given object in my main MOC and the ID for the changes to merge for that object were both temporary, but different. But neither were the permanent ID that they should have been. A search on Stack Overflow for that issue led me to this answer https://stackoverflow.com/a/11996957/1892468 which gives a workaround for a known Core Data bug.

So the problem wasn't that I was doing it wrong, it was that Core Data was not doing what it said it would do. The workaround is that during the save operation on the main object context I added the following code prior to calling save.

if ctx.insertedObjects.count > 0 {
    do {
        try ctx.obtainPermanentIDsForObjects(Array(ctx.insertedObjects))
    } catch {
        log.error("Unable toobtain permanent ids for inserts")
    }
}

That fixed it! So what I had originally been observing was that the merge was not actually taking place. There were 2 objects alive for what was supposed to be one.




回答2:


You could simply subscribe to NSManagedObjectContextDidSaveNotification of the sync context and then merge changes into UI context by calling -mergeChangesFromContextDidSaveNotification:.



来源:https://stackoverflow.com/questions/21130866/nsmanagedobject-values-are-correct-then-incorrect-when-merging-changes-from-par

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!