NSManagedObject值是正确的,然后在将更改从父级NSManagedObjectContext合并到子级NSManagedObjectContext时不正确 [英] NSManagedObject values are correct, then incorrect when merging changes from parent to child NSManagedObjectContext

查看:287
本文介绍了NSManagedObject值是正确的,然后在将更改从父级NSManagedObjectContext合并到子级NSManagedObjectContext时不正确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用2 NSManagedObjectContext 时遇到核心数据问题,在不同的线程上运行,并将更改从父级迁移到子级。实质上,我能够将更改从父级拉到子级,但在此之后,更改将立即丢​​失。

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];            
}];

syncManagedObjectContext是父上下文,并且是syncManager与服务器执行同步的位置。它收集用户修改的对象,将更改发送到服务器,并将收到的更改合并回来。 syncManagedObjectContext还将其数据发送到 PersistentStoreCoordinator 以存储在 SQLite 中。上下文在后台线程使得同步和存储不会阻塞主线程。下面是如何创建它:

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];
}];



同步逻辑流程



当syncManager从主上下文处理NSManagedObjectContextObjectsDidChangeNotification时启动。下面是发生的大致情况:

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处理 NSManagedObjectContextObjectsDidChangeNotification 知道对象已经在主线程上下文中被改变。

  2. 当syncManager收到 NSManagedObjectContextDidSaveNotification 以表示保存已完成时,它从同步上下文收集新更改的对象,并将更改发送到服务器。然后它保存在同步MOC,它将数据发送到SQLite。请注意,每个对象都有一个uuid字段,我创建为可移植的id - 不要与Core Data的objectID混淆,以及由服务器提供的lastSynced时间戳。

  3. 服务器以已发送对象的更新时间戳以及已发生的任何其他更改来响应。在最简单的情况下,说明问题,接收的是一组记录,由syncManager刚刚发送的对象的uuid和更新的lastSynced时间组成。

  4. 对于每个更新,syncManager会更新syncContext中的对象,并将对象(而不是uuid)的NSManagedObject对象ID存储在数组中。

  5. syncManager然后对同步MOC执行保存将数据写入磁盘并发布消息以向主MOC提供更新对象的objectID数组。此时,如果我对syncMOC中的所有实体进行提取并将它们转储到日志中,则它们都具有正确的值。此外,如果我查看磁盘上的SQLite数据库,它也有正确的值。

  6. 这里是简化的代码(一些错误检查和非必要的东西删除)如何更新合并在主线程上,有关于发生了什么的注释:(注意:我一直小心在代码中使用performBlock,它出现从跟踪调试器,一切正在发生在正确的线程。)

  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!
    }];

 }

如果你错过了这个问题,在 NSLog 语句之后的注释。对象最初具有来自父上下文的正确值,但随后它们变得不正确。看看时间戳,具体来说。

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.

有没有人知道为什么会发生这种情况?我应该注意到,做fetch的事情到底是调试的一部分。我注意到在程序中持有的NSManagedObject没有正确的值,即使我看到的东西在上面的代码和通过uniquing正确更新,他们也应该更新。我想,可能发生的是,我正在创建额外对象与正确的值,而旧的仍在周围。但是,fetch显示正确的对象,只有正确的对象,只有坏的值。

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.

还有一件事,如果我在父此函数运行后的上下文,它显示正确的值, SQLite

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.

任何帮助非常感谢! / p>

Any help is much appreciated!

推荐答案

我终于找到了这个问题的答案,希望它可以帮助别人。

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

我注意到的一点是,返回主上下文的对象ID不正确
核心数据ID - 它们应该是永久的,但不是。事实上,在合并期间,I
意识到在我的主MOC中的给定对象的ID和对于
合并的对象的ID是临时的,但不同。但也不是永久的ID,他们应该是。
在Stack Overflow上搜索该问题导致我
此答案 http://stackoverflow.com/a / 11996957/1892468 ,它提供了一个已知的Core
数据错误的解决方法。

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 http://stackoverflow.com/a/11996957/1892468 which gives a workaround for a known Core Data bug.

我的做错了,这是Core Data没有做什么它
说它会做。解决方法是在主对象上下文的保存操作期间,我在调用save之前添加了以下
代码。

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.

这篇关于NSManagedObject值是正确的,然后在将更改从父级NSManagedObjectContext合并到子级NSManagedObjectContext时不正确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆