核心数据:如何在两个 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 之间合并插入/更新/删除,同时将合并保持为可撤消的步骤?