核心数据:如何在两个 NSManagedObjectContext 之间合并插入/更新/删除,同时将合并保持为可撤消的步骤?

Posted

技术标签:

【中文标题】核心数据:如何在两个 NSManagedObjectContext 之间合并插入/更新/删除,同时将合并保持为可撤消的步骤?【英文标题】:Core Data: How to merge inserts/updates/deletes between two NSManagedObjectContext's while maintaining the merge as an undoable step? 【发布时间】:2011-07-01 12:29:07 【问题描述】:

我有一个基于文档的 Core Data 应用程序(在 Mac OS X 10.5 及更高版本上运行),我试图在主线程上使用两个 NSManagedObjectContext。我想将次要上下文中所做的更改合并到我的主要(主要)上下文中。此外,我希望从辅助上下文中合并的更改是可撤消的,并导致文档被标记为“脏”。我想我的问题类似于“Undoing Core Data insertions that are performed off the main thread”但是,ATM,我没有使用不同的线程。

我一直在观察NSManagedObjectContextDidSaveNotification(在调用-[self.secondaryContext save:] 时从第二个上下文发送),如下所示:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(mocDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:self.secondaryContext];

在观察者调用的-mocDidSave: 方法中,我尝试在主要上下文中使用-[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] 将来自次要上下文的更改合并到主要上下文中:

- (void)mocDidSave:(NSNotification *)notification

    [self.primaryContext mergeChangesFromContextDidSaveNotification:notification];

但是,虽然插入的对象很容易出现在我的数组控制器中,但文档并未标记为脏,并且新添加的托管对象的 isInserted 属性未设置为 YES。插入(到主要上下文中)也是不可撤消的。

对任何插入的对象进行重新故障至少会将文档标记为脏,但插入仍然不可撤消:

- (void)mocDidSave:(NSNotification *)notification

    [self.primaryContext mergeChangesFromContextDidSaveNotification:notification];

    for (NSManagedObject *insertedObject in [[notification userInfo] objectForKey:NSInsertedObjectsKey]) 
        [self.primaryContext refreshObject:[self.primaryContext existingObjectWithID:[insertedObject objectID] error:NULL] mergeChanges:NO];
    

W.r.t. -mocDidSave:,使用自定义实现我的结果稍微好一点:

- (void)mocDidSave:(NSNotification *)notification

    NSDictionary *userInfo = [notification userInfo];

    NSSet *insertedObjects = [userInfo objectForKey:NSInsertedObjectsKey];
    if ([insertedObjects count]) 
        NSMutableArray *newObjects = [NSMutableArray array];
        NSManagedObject *newObject = nil;
        for (NSManagedObject *insertedObject in insertedObjects) 
            newObject = [self.primaryContext existingObjectWithID:[insertedObject objectID] error:NULL];
            if (newObject) 
                [self.primaryContext insertObject:newObject];
                [newObjects addObject:newObject];
            
        

        [self.primaryContext processPendingChanges];

        for (NSManagedObject *newObject in newObjects) 
            [self.primaryContext refreshObject:newObject mergeChanges:NO];
        
    

    NSSet *updatedObjects = [userInfo objectForKey:NSUpdatedObjectsKey];
    if ([updatedObjects count]) 
        NSManagedObject *staleObject = nil;
        for (NSManagedObject *updatedObject in updatedObjects) 
            staleObject = [self.primaryContext objectRegisteredForID:[updatedObject objectID]];
            if (staleObject) 
                [self.primaryContext refreshObject:staleObject mergeChanges:NO];
            
        
    

    NSSet *deletedObjects = [userInfo objectForKey:NSDeletedObjectsKey];
    if ([deletedObjects count]) 
        NSManagedObject *staleObject = nil;
        for (NSManagedObject *deletedObject in deletedObjects) 
            staleObject = [self.primaryContext objectRegisteredForID:[deletedObject objectID]];
            if (staleObject) 
                [self.primaryContext deleteObject:staleObject];
            
        

        [self.primaryContext processPendingChanges];
    

这会导致我的数组控制器得到更新,文档被标记为脏,并且插入和删除是不可撤销的。但是,我仍然遇到无法撤消的更新问题。我是否应该手动循环遍历所有 updatedObjects 并使用 -[NSManagedObject changedValues] 重新应用主上下文中的更改?

当然,这种自定义实现会在主要上下文上重复从次要上下文中的大量工作。是否有任何其他/更好的方法可以在两个上下文之间进行合并,同时将合并保持为可撤消的步骤?

【问题讨论】:

【参考方案1】:

如果您不使用单独的线程,那么您实际上不需要单独的上下文。在同一个线程上使用两个上下文会增加复杂性而不会获得任何东西。如果您不确定是否会使用线程,那么我强烈建议您只使用一个上下文。过早的优化是万恶之源。

保存重置撤消管理器,因此您不能使用NSManagedObjectContextDidSaveNotification 执行任何可以撤消的操作。正如您发现的那样,您可以欺骗应用程序认为文档是脏的,但您不能强制撤消管理器记住上次保存之后的内容。

要获得无限撤消,唯一的方法是在幕后保存文档的多个版本。 IIRC,您还可以序列化撤消管理器,以便可以将其写入文件并重新加载以回溯。

【讨论】:

TechZen,非常感谢您的回答和建议!我的情况有点复杂,我应该在我的原始帖子中更好地解释这一点,抱歉。我计划发布一个关于我的情况的专门问题,但我想我应该在这里提供一些快速的背景信息:在我的公司,我的任务是开发现有 Windows 应用程序的 Mac“版本”(已经有很多业务逻辑和一个现有的文件格式,也应该用于 Mac 版本)。 (继续评论)为了避免出现“坏端口”(而不是带有原生 GUI 的一流 Mac 程序),我们决定开发 GUI(及其在 Cocoa 中使用 Core Data、Bindings & Co. 附带的窗口/视图控制器代码)。然而,为了利用现有的文件格式和功能,我们考虑将“Windows 引擎”作为自定义存储(例如 NSAtomicStore)暴露给 Core Data子类)。此外,这个 Windows 引擎的部分业务逻辑将通过 Mono + MonoMac 暴露给 Cocoa 应用程序。 (继续评论)也就是说,我意识到将另一个“引擎”作为自定义存储暴露给 Core Data 是很不寻常的。我知道这可能不是最好的主意,并且可能会造成内存和同步问题。然而,在某种程度上,我们的情况对我来说似乎与拥有两个独立的托管对象上下文(通常在不同的线程中)并没有太大的不同,其中变化独立发生并且需要同步。因此,我在考虑是否可以针对我的情况使用相同的“上下文合并”技术。 如果你已经在 Mono 中有一个工作模型层,我建议完全跳过 Core Data。为现有模型编写控制器代码比将现有模型硬塞到 Core Data 中更容易。您在两个线程上使用两个上下文的情况很常见,但您仍然无法在不跳过很多环节的情况下回滚保存。 我见过人们使用分进程应用程序来移植一个困难的模型。您将应用程序拆分为两个进行通信的进程。一个进程运行模型,在本例中是 Mono,而另一个进程是一个纯 Cocoa 应用程序,具有所有漂亮的 UI 优点。

以上是关于核心数据:如何在两个 NSManagedObjectContext 之间合并插入/更新/删除,同时将合并保持为可撤消的步骤?的主要内容,如果未能解决你的问题,请参考以下文章

我如何在单个提取请求中调用两个 NSPredicate。核心数据 iOS Swift

如何对两个不同的核心数据对象进行排序?

核心数据:如何在两个 NSManagedObjectContext 之间合并插入/更新/删除,同时将合并保持为可撤消的步骤?

核心数据迁移——如何将两个实体合二为一

如何在其他核心上运行每个线程?

如何将 UIButton 值存储在核心数据中?