27

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

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

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

为了解决这个问题,我尝试使用子上下文,如此所述。在阅读了我在 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. 当我的用户保存时,它会向其委托(我的主视图控制器)发送一条消息。委托传递了添加的对象,并且它必须在主上下文中找到相同的对象。但是当我在主要上下文中寻找它时,它没有找到。主要上下文确实包含实体 - 我可以记录其详细信息并确认它存在 - 但地址不同?如果这注定要发生(为什么?),我如何在保存后在主上下文中找到添加的对象?

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

4

3 回答 3

47

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

  1. 传统上,您将sNSMainQueueConcurrencyType用于NSManagedObjectContext与主线程关联,将NSPrivateQueueConcurrencyTypes 用于子上下文。子上下文不需要匹配其父上下文。如果您不指定类型,则NSConfinementConcurrencyType所有s 都默认为这是。NSManagedObjectContext它基本上是“我将为核心数据管理自己的线程”类型。
  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. 您需要objectWithID在 managedObjectContext 上使用某种形式。它们是在上下文之间传递对象的最安全(也是唯一)的方式。正如 Tom Harrington 在评论中提到的那样,您可能想要使用existingObjectWithID:error:虽然,因为objectWithID:始终返回一个对象,即使您传递了无效的 ID(这可能导致异常)。更多详情:链接
于 2013-01-11T20:19:58.780 回答
6
  1. 如果使用父/子模式,通常使用 声明父上下文,NSMainQueueConcurrencyType使用NSPrivateQueueConcurrencyType. NSConfinementConcurrencyType用于经典的穿线模式。

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

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

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

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

于 2013-01-11T19:40:31.067 回答
6

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

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

然后,您可以保存 mainContext 本身。

也许还有,

[childContext reset];

如果要重置临时上下文。

于 2013-01-11T19:46:38.290 回答