【问题标题】:NSManagedObjectContext Reset appears to be losing data?NSManagedObjectContext 重置似乎正在丢失数据?
【发布时间】:2012-09-26 12:17:22
【问题描述】:

我的核心数据存储包含 51 个 Entry 实体,它们的 message 属性读取“测试”。为此执行 NSFetchRequest 确认它们都在那里。

不过,我的方法的另一部分将用于内存密集型目的,处理大块 NSData,因此我需要经常调用[oldContext reset];

我有一个内存密集型方法,可以从我的 MOC 访问大量 NSData。因此,它会定期调用[oldContext reset];。没有这一行,它就会耗尽内存。

我发现虽然使用它,但它并没有返回正确的结果。为了测试这一点,我注释掉了数据密集型代码,留下了返回 message 属性的代码,其中 51 个设置为“测试”(由单独的 NSFetchRequest 确认)。

但是,使用 [oldContext reset];,它只返回 6 个结果,消息设置为“测试”。这是我正在使用的代码:

    NSFetchRequest *oldFetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *oldEntryEntity = [NSEntityDescription entityForName:@"Entry"
                                                      inManagedObjectContext:oldContext];
    [oldFetchRequest setEntity:oldEntryEntity];
    [oldFetchRequest setFetchBatchSize:10];
    [oldFetchRequest setIncludesPropertyValues:NO];
    NSArray *entrys = [oldContext executeFetchRequest:oldFetchRequest error:&error];

    int totalEntries = [oldContext countForFetchRequest:oldFetchRequest error:nil];

    int i = 0;

    while (i < totalEntries) {
        @autoreleasepool {

            Entry *entry = [entrys objectAtIndex:i];

            NSLog(@"message 1: %@", [entry valueForKey:@"message"]);


            [oldContext reset];


            i++;
        }
    }

对于为什么它没有给出它应该做的 51 个“测试”结果有什么想法吗?

【问题讨论】:

    标签: iphone objective-c ios core-data managedobjectcontext


    【解决方案1】:

    尝试将获取批量大小设置为 1 而不是当前的 10。

    我对这种情况的解释如下。

    1. executeFetchRequest... 将 10 条记录提取到上下文中。
    2. 但是,通过重置上下文,所有记录都将丢失。所以,只有 10 条记录中的第一个记录。请注意,在上下文重置之前记录第一条记录。
    3. 由于我们总共有 51 条记录,所以 51 % 10 = 6 记录仍然存在。

    【讨论】:

    • 好吧,这解决了我的问题,但它使我的应用程序再次崩溃 - 内存使用量增加了 10 倍。
    • 是简化的示例应用程序崩溃了吗?还是您原来的应用有一些额外的功能?
    • 原件。两者的内存使用量都增加了 10 倍。
    【解决方案2】:
    NSFetchRequest *oldFetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *oldEntryEntity =
        [NSEntityDescription entityForName:@"Entry"
                    inManagedObjectContext:oldContext];
    [oldFetchRequest setEntity:oldEntryEntity];
    [oldFetchRequest setFetchBatchSize:10];
    [oldFetchRequest setIncludesPropertyValues:NO];
    NSArray *entrys = [oldContext executeFetchRequest:oldFetchRequest error:&error];
    int totalEntries = [oldContext countForFetchRequest:oldFetchRequest error:nil];
    

    上面的代码在 MOC oldContext 上执行一个获取请求。因此,entrys 数组将包含数据库中与请求匹配的每个对象的托管对象。此外,由于设置了批量大小,提取会导致您的对象批量大小出错。

    作为一个实验来确保自己发生了什么,添加这些日志语句...

    NSLog(@"entrys count = %u", entrys.count);
    for (NSManagedObject *entry in entrys) {
        NSLog(@"entry: %@", entry);
    }
    

    你看到那个数组中有什么吗?现在有意义吗?

    让我们看看剩下的代码。

    int i = 0;
    while (i < totalEntries) {
        @autoreleasepool {
            // You get the i-th entry.  It will be a managed object.  It could be a fault
            // or it could be a fully hydrated object.  Based on your batch size, the
            // first ten (0 <= i < 10) will be complete objects.
            Entry *entry = [entrys objectAtIndex:i];
    
            // Log the "message" attribute.  By calling valueForKey, the object will be
            // faulted into memory if it is a fault.  Since your batch size is 10,
            // this will make sure 10 objects are faulted if one is needed.
            NSLog(@"message 1: %@", [entry valueForKey:@"message"]);
    
            // Resetting the entire context blows away everything in the context.
            // Calling reset is a hard call, and should not be done if you have
            // references to the objects in the context.
            [oldContext reset];
    
            i++;
        }
    }
    

    我会建议一种不同的方法。调用 reset 不适用于您将保留对象的上下文。

    有几种选择。您可以创建一个子上下文,在那里进行临时工作,然后释放上下文。它将释放它使用的所有内存。

    你可以选择性地使用

    - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag
    

    如果flagNO,这会将对象重新变为故障,从而释放其内存。请注意,这也存在一些固有的危险,尤其是在您管理好关系的情况下。

    还有其他一些选择,但如果不知道您的最终目标是什么……很难说什么是最有益的。

    我强烈建议您阅读与这些调用相关的所有文档。事实上,虽然 Core Data 非常复杂,但它确实有一些“脆弱”的交互作用,如果你做任何不平凡的事情,你应该知道这些。

    我强烈建议阅读所有核心数据文档。它解决了您在项目中遇到的所有问题。

    【讨论】:

      【解决方案3】:

      根据 Apple 文档,这是 ManagedObjectContext 重置的作用:

      • 所有接收者管理的对象都被“遗忘”了。如果您使用此方法,则应确保同时丢弃​​对使用接收器获取的任何托管对象的引用,因为之后它们将无效。

      因此,在您的代码中,您是在告诉 oldContext 每次 while 循环迭代时忘记其所有对象。

      你没有说 NSData 来自 MOC 中的哪个位置,但如果它来自 Entry 实体,我会说你有两个选择:

      1) 不要每次都重置 MOC,而是使用 NSManagedObjectContext 的 refreshObject:mergeChanges: 方法。这将使实体重新出现故障并释放内存。比如:

      while (i < totalEntries) {
          @autoreleasepool {
      
              Entry *entry = [entrys objectAtIndex:i];
      
              NSLog(@"message 1: %@", [entry valueForKey:@"message"]);
      
      
              [oldContext refreshObject:entry mergeChanges:NO];
      
      
              i++;
          }
      }
      

      2) 不要在 Core Data 实体中存储大量数据。相反,将数据写入文件系统并保留对实体中数据路径的引用。那将是我的首选方法。看看“大数据对象”部分下的苹果Core Data Performance

      【讨论】:

      • 由于我不明白的原因,数字 1 不起作用。它不会释放任何内存,并且会像往常一样在同一时间崩溃。至于第 2 点,这是处理来自应用商店中现有应用的数据,因此现在不是一个选项。
      • 您是否将 NSData 存储在 Entry 对象中?如果在 while 循环中注释掉 NSLog 行会发生什么?数据在某个地方出现故障,并且不会再次退出。
      猜你喜欢
      • 2015-12-23
      • 2018-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多