正确实现父/子NSManagedObjectContext

时间:2013-01-11 18:20:38

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

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

我现在已经引入了一个'导入'功能,可以切换到我的应用程序(使用URL方案)并添加一个实体。由于其中一个模态可能是打开的,因此此时保存上下文是不安全的。即使用户取消,也会保存为模态创建的瞬态对象,并且无法保证稍后将保存删除(来自取消操作) - 用户可能会退出该应用程序。

同样,每当我的应用程序退出时,我都无法保存。如果模式在该点打开,则临时对象将被错误地保存。

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

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

  2. (附带问题)如果我使用iVar类,为什么我只能让它工作?我以为我应该能够在创建它的方法中声明临时上下文,然后使用entity.managedObjectContext来引用它。但是当我访问它时它似乎是零?如果我改为使用iVar来保存参考,那么这已得到纠正。

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

    //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. 当我的用户保存时,它会向其委托(我的主视图控制器)发送一条消息。委托传递添加的对象,它必须在主上下文中找到相同的对象。但是当我在主要环境中寻找它时,却找不到它。主要上下文确实包含实体 - 我可以记录其详细信息并确认它在那里 - 但地址是不同的?如果要发生这种情况(为什么?),如何在保存后在主上下文中找到添加的对象?

  5. 感谢您的任何见解。很抱歉有一个很长的,多部分的问题,但我认为有人可能已经解决过所有这些问题。

3 个答案:

答案 0 :(得分:46)

父/子MOC模型是核心数据的一个非常强大的功能。它简化了我们以前必须处理的古老并发问题。但是,正如您所说,并发不是您的问题。回答你的问题:

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

    - (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");
            }
        }];
    }
    

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

    1. 您需要在managedObjectContext上使用某种形式的objectWithID。它们是在上下文之间传递对象的最安全(也是唯一的)方法。正如Tom Harrington在评论中提到的那样,您可能希望使用existingObjectWithID:error:,因为objectWithID:总是返回一个对象,即使您传入了无效的ID(可能导致异常)。有关详细信息:Link

答案 1 :(得分:6)

  1. 如果您使用父/子模式,通常会使用NSMainQueueConcurrencyType声明父上下文,并使用NSPrivateQueueConcurrencyType声明子上下文。 NSConfinementConcurrencyType用于经典线程模式。

  2. 如果你想保留上下文,你需要以某种方式强烈引用它。

  3. 您只需在子上下文中调用save方法将更改推送到父上下文,如果要保留数据,也可以在父上下文中调用save。你不需要在一个街区内这样做。

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

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

答案 2 :(得分:6)

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

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

然后您可以保存mainContext本身。

也许,

[childContext reset];

如果要重置临时上下文。