【问题标题】:Correct implementation of parent/child NSManagedObjectContext正确实现父/子 NSManagedObjectContext
【发布时间】:2012-12-26 09:32:23
【问题描述】:

我的应用有时会在托管对象上下文中插入不一定要保存的对象。例如,当我启动“添加实体”模式时,我会创建一个托管对象并将其分配给该模式。如果用户从该模式保存,我保存上下文。如果他取消,我会删除该对象并且不需要保存。

我现在引入了一个“导入”功能,它可以切换到我的应用程序(使用 URL 方案)并添加一个实体。因为这些模态之一可能是打开的,所以此时保存上下文是不安全的。为模态创建的瞬态对象将被保存,即使用户取消,也不能保证稍后会保存删除(来自取消操作) - 用户可能会退出应用程序。

同样,我不能在我的应用退出时简单地保存。如果此时模态框处于打开状态,则临时对象将被错误地保存。

为了解决这个问题,我尝试使用子上下文,正如 here 所讨论的那样。在阅读了我在 SO 上能找到的所有内容后,我还有几个问题:

  1. 我应该为每个上下文使用哪种并发类型?请记住,我这样做不是为了性能/线程优势。我知道如果要拥有子上下文,我不能将 NSConfinementConcurrencyType 用于主上下文,但我不确定其他两个选项中的哪一个最适合。对于子上下文,是否需要匹配?或者我什至可以在这里使用禁闭类型吗?我尝试了多种组合,似乎一切正常,但我想知道哪种组合适合我的要求。

  2. (附带问题)为什么我只能在使用 iVar 类时才能使用它?我认为我应该能够在创建它的方法中声明临时上下文,然后稍后使用 entity.managedObjectContext 引用它。但是当我来访问它时它似乎为零?如果我改为使用 iVar 来保存引用,则可以纠正此问题。

  3. 什么是正确的方法或将更改传播到主要上下文?我已经看到各种 cmets 在每个上下文中使用不同的块包装实现。这取决于我的并发类型吗?我目前的版本是:

    //save the new entity in the temporary context
    NSError *error = nil;
    if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");}
    
    //propogate the save to the main context
    [self.mainContext performBlock:^{
        NSError *error2 = nil;
        if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");}
    }];
    
  4. 当我的用户保存时,它会向其委托(我的主视图控制器)发送一条消息。委托传递了添加的对象,并且它必须在主上下文中找到相同的对象。但是当我在主要上下文中寻找它时,它没有找到。主要上下文确实包含实体 - 我可以记录其详细信息并确认它存在 - 但地址不同?如果这注定会发生(为什么?),我如何在保存后在主上下文中找到添加的对象?

感谢您的任何见解。抱歉,我提出了一个冗长的多部分问题,但我认为之前可能有人已经解决了所有这些问题。

【问题讨论】:

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


    【解决方案1】:

    父/子 MOC 模型是 Core Data 的一个非常强大的特性。它极大地简化了我们过去必须处理的古老的并发问题。但是,正如您所说,并发不是您的问题。回答您的问题:

    1. 传统上,您将NSMainQueueConcurrencyType 用于与主线程关联的NSManagedObjectContext,将NSPrivateQueueConcurrencyTypes 用于子上下文。子上下文不需要匹配其父上下文。如果您不指定类型,NSConfinementConcurrencyType 是所有 NSManagedObjectContexts 的默认值。它基本上是“我将为 Core Data 管理自己的线程”类型。
    2. 没有看到您的代码,我的假设是您创建子上下文的范围结束并被清理。
    3. 使用父/子上下文模式时,您需要使用块方法。使用块方法的最大好处是操作系统将处理将方法调用分派给正确的线程。您可以使用performBlock 进行异步执行,或使用performBlockAndWait 进行同步执行。

    你可以这样使用:

    - (void)saveContexts {
        [childContext performBlock:^{
            NSError *childError = nil;
            if ([childContext save:&childError]) {
                [parentContext performBlock:^{
                    NSError *parentError = nil;
                    if (![parentContext save:&parentError]) {
                        NSLog(@"Error saving parent");
                    }
                }];
            } else {
                NSLog(@"Error saving child");
            }
        }];
    }
    

    现在,您需要记住,在您保存之前,在子上下文中所做的更改(例如插入的实体)将无法用于父上下文。对于子上下文,父上下文是持久存储。保存时,您将这些更改传递给父级,然后父级可以将它们保存到实际的持久存储中。将传播更改保存上一级。另一方面,提取到子上下文中会将数据拉到每个级别(通过父级和子级)

    1. 您需要在 managedObjectContext 上使用某种形式的objectWithID。它们是在上下文之间传递对象的最安全(也是唯一)的方式。正如 Tom Harrington 在 cmets 中提到的,您可能希望使用 existingObjectWithID:error:,因为 objectWithID: 始终返回一个对象,即使您传入了无效的 ID(这可能导致异常)。更多详情:Link

    【讨论】:

    • 根据上面安迪的回答,我测试了一个没有使用块进行保存的版本,到目前为止它正在工作(并不是说它是正确的)。使用块实现的目的是什么,如果我继续简单地在上下文中调用 save 会出现什么问题?
    • 至于#2,我认为它不应该真正属于这个问题——我仍然对我插入的对象有一个有效的引用——它不是保持它的 moc 活着吗?
    • 正如我在上面#3 中所说,使用基于块的方法的目的是确保您的消息被分派到正确的线程。这只有在您进行多线程核心数据工作时才真正重要。顺便说一句,您还可以使用 performBlock 异步执行工作(如果 MOC 在不同的线程上。在主线程上的 MOC 上调用 performBlock 只会将代码分派到主线程)
    • 这是有效的,当然你需要照顾你的线程。例如,如果您想将父上下文保存在子上下文块中,那么您需要为其使用单独的块。但是如果你想将父上下文保存在子块之外,你不需要使用块,因为你回到了主线程。
    • 一个很好的答案。我建议您使用existingObjectWithID:error: 而不是objectWithID:,因为objectWithID: 始终是一个对象,即使您传入无效的ID(这可能导致异常)。最好确保只获取已知存在的对象。
    【解决方案2】:
    1. 如果使用父/子模式,通常使用NSMainQueueConcurrencyType 声明父上下文,使用NSPrivateQueueConcurrencyType 声明子上下文。 NSConfinementConcurrencyType 用于经典线程模式。

    2. 如果你想保留上下文,你需要一个强引用它。

    3. 您只需在子上下文中调用 save 方法将更改推送到父上下文,如果要持久化数据,您也可以在父上下文上调用 save。您不需要在一个块内执行此操作。

    4. 有几种方法可以从上下文中获取特定对象。我不能告诉你哪一个适合你的情况,试试看:

      - objectRegisteredForID:

      - objectWithID:

      - existingObjectWithID:error:

    【讨论】:

    • 谢谢 - 为什么我不需要使用块的任何背景?在其他地方看到相互矛盾的信息。
    • 如果您使用基于队列的并发选项之一,您确实需要使用块调用以确保安全。跳过它们通常会起作用,但并非总是如此。唯一的例外是,如果您使用了NSMainQueueConcurrencyType 并且,您正在从主队列进行呼叫。在这种情况下,它是可选的。
    【解决方案3】:

    我也遇到过类似的问题,这里是您部分问题的答案- 1.你应该可以使用并发类型NSPrivateQueueConcurrencyTypeNSMainQueueConcurrencyType 2. 假设您创建了一个临时上下文tempContext 和父上下文mainContext(这是假设iOS5)。在这种情况下,您只需将托管对象从 tempContext 移动到 mainContext by-

    object = (Object *)[mainContext objectWithID:object.objectID];
    

    然后您可以保存 mainContext 本身。

    也许还有,

    [childContext reset];
    

    如果你想重置临时上下文。

    【讨论】:

      猜你喜欢
      • 2015-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 1970-01-01
      • 2016-08-24
      相关资源
      最近更新 更多