【问题标题】:Undoing Core Data insertions that are performed off the main thread撤消在主线程之外执行的核心数据插入
【发布时间】:2011-06-23 20:46:38
【问题描述】:

我正在编写一些使用NSOperation 导入数据的代码。我希望用户能够撤消在导入操作期间创建的 NSManagedObject 实例。

据我所知,在主线程之外执行的任何操作都无法使用 NSManagedObjectContext -undoManager。从核心数据编程指南部分使用线程限制支持并发,我们有这两个条件:

  1. 只应传递 objectID 托管对象上下文之间(在 单独的线程)
  2. 托管对象 必须先保存在上下文中 可以使用 objectID。

这是有道理的,因为托管对象需要从私有存储 (NSManagedObjectContext) 移动到公共存储 (NSPersistentStore) 才能共享。

不幸的是,-save: 消息还会导致撤消堆栈中的所有托管对象被删除。从同一指南的使用核心数据进行内存管理部分:

有待处理的托管对象 更改(插入、删除或 更新)由其上下文保留 直到他们的上下文被发送一个保存:, 重置、回滚或释放消息, 或适当数量的撤消 撤消更改。

我已经尝试了几种方法来解决这个限制,但一切最终都会导致主线程上发生的大部分工作(以及旋转的沙滩球)。任何关于撤消使用主线程创建的对象的线索将不胜感激。

--

已提交增强雷达:rdar://problem/8977725

【问题讨论】:

  • 为什么不记录这个操作插入的所有托管对象 ID 的日志呢?这样,如果用户想要回滚它,您可以简单地迭代并删除对象。如果您还必须回滚突变,那么记录键/值更改也不会太难。似乎比尝试将其插入撤消管理器更简单。
  • 关于上述评论,您建议如何处理重做?

标签: objective-c cocoa core-data nsoperation nsmanagedobjectcontext


【解决方案1】:

这个答案可能会有点来回。如果我正确理解了这个问题,您正在执行导入,但是当导入完成后,您希望用户能够选择从导入中保存的内容?

如果这不正确,请修正我的假设,我会更新这个答案。

如果是正确的,那么你可以做的是:

  1. 将您的背景对象创建更改为

    NSEntityDescription *myEntity = ... //Entity from your context
    [[NSManagedObject alloc] initWithEntity:myEntity
             insertIntoManagedObjectContext:nil];
    
  2. 将这些实体存储在一个数组中。
  3. 根据需要将实体传回主线程。
  4. 释放您不想保留的任何对象
  5. 如果您想保留任何信息,请致电 [myMainContext insertObject:managedObject]。
  6. 在NSManagedObjectContext 上执行保存。

由于这些实体不是NSManagedObjectContext 的一部分,但它们只存在于内存中,并且应该是线程安全的,因为它们尚未绑定到NSManagedObjectContext。

这当然是理论上的,需要测试。但是它应该可以实现您的目标。

【讨论】:

  • [initWithEntity:entity insertIntoManagedObjectContext:nil] 是否正确且有效的代码?你不应该为那些临时对象创建一个内存中的 MOC 吗?
  • 没有必要。您需要 MOC 来解析 NSEntityDescription,但 MO 不需要与 MOC 相关联才能存在。它只需要与要保存的 MOC 相关联。
  • 很高兴知道。感谢您的信息。
  • 感谢您的回复,马库斯!我没有考虑过这种方法,但我认为它不会在这种情况下起作用:在导入期间创建的一些实体是有条件地创建的(通过在上下文中查询托管对象的 NSFetchRequest。)我已经尝试过将这些查询移到主线程,但是跨线程边界获取结果很棘手(-performSelectorOnMainThread 返回一个 void。)
  • 我现在正在研究的一种方法是在后台导入操作使用同一个实例时将 NSManagedObjectContext 锁定在主线程中。我的目标是在没有 -save 的情况下将对象放入上下文中:(以便撤消将起作用)并且不会得到任何旋转的沙滩球(因为要导入很多文件。)主线程上没有工作这需要在导入期间发生(更新进度指示器除外。)
【解决方案2】:

您很可能已经考虑过这一点,并且您可能只是在寻找使用现有 undoManager 的解决方案,但以防万一:

由于您插入对象而不是更新现有对象,因此您可以在导入每个批次时使用事务 ID 标记它们,在撤消的情况下在后台线程中删除它们。一个简单的递增NSNumber 就足够了。

不优雅,但可行。

【讨论】:

    【解决方案3】:

    一种选择可能是使您的导入线程持久化。即使线程完成导入,它也会进入空闲循环状态。这样,您的线程化 ManagedObjectContext 将持久保存在正确的线程中。然后当用户希望撤消更改时,向线程发送消息以使用撤消管理器。

    【讨论】:

    • 这里的问题是托管对象上下文不知道彼此插入的对象,直到 -save: 发生。此时撤消堆栈被拆除......
    【解决方案4】:

    假设您为后台线程使用单独的上下文,并且一旦完成,将[[backgroundContext undoManager] undo] 推入前台线程的撤消堆栈?我从来没有尝试过这样的事情,但是我想不出它不应该起作用的原因。

    【讨论】:

    • “[[backgroundContext undoManager] undo] 到前台线程的撤消堆栈”是什么意思?据我所知,撤消堆栈的内容是私有的……
    • [[[foregroundContext undoManager] prepareWithInvocationTarget:[backgroundContext undoManager]] undo]; (其实你也需要保存 backgroundContext...)
    • 这有几个问题:backgroundContext 是一个短期线程,foregroundContext 无法长期定位。此外,一旦 backgroundContext 发送 -save: 撤消堆栈上没有任何内容可供前台任务使用。
    【解决方案5】:

    不是专家,但我认为您需要做的是创建第二个上下文来执行操作,然后将这两个上下文合并在一起。您应该能够将合并作为撤消步骤进行管理。请注意,就用户而言,这仅在您将整个操作集视为一个撤消步骤时才有效。

    【讨论】:

    • 我已经在做 -mergeChangesFromContextDidSaveNotification: 将导入上下文中的更改传播到主线程上的上下文。问题是合并使用已经在持久存储中的数据调整对象图。主线程上的 NSManagedObjectContext 没有任何需要写入持久存储的 -insertedObjects 或 -updatedObjects。
    猜你喜欢
    • 1970-01-01
    • 2010-11-17
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多