导入大型数据集时的核心数据内存使用情况

时间:2012-10-29 20:21:35

标签: ios core-data memory

我现在因为一个讨厌的核心数据问题而被困了大约两个星期。我阅读了很多博客文章,文章和SO问题/答案,但我仍然无法解决我的问题。

我进行了大量的测试,并且能够将更大的问题减少到更小的问题。 这将是一个很大的解释,所以请留意我!

问题 - datamodel

我必须关注 datamodel

对象A与对象B具有一对多关系,它与对象C具有另一个一对多关系。由于核心数据建议,我必须创建反向关系,因此B的每个实例都指向其父A和对于指向其父B的C也是如此。

A <->> B <->> C

问题 - MOC设置

为了保持响应能力,我创建了一个三级的managedObjectContext结构。

  1. 父MOC - 使用NSPrivateQueueConcurrencyType在自己的私有线程上运行,与persistentStoreCoordinator
  2. 紧密相关
  3. MainQueue MOC - 使用NSMainQueueConcurrencyType在mainThread上运行并拥有父MOC 1
  4. 对于每个解析操作,我创建了第三个MOC,它也有自己的私有队列并且具有父mainQueue MOC
  5. 我的主数据控制器作为观察者添加到MOC 2的NSManagedObjectContextDidSave通知中,因此每次MOC 2在MOC1上保存performBlock:时都会触发执行保存操作(异步,因为{{1 }})。

    问题 - 解析

    为了将大型JSON文件解析为我的Core Data结构,我编写了一个循环解析器。该解析器首先创建一个新的MOC(3)。然后它获取对象A的数据并解析其属性。然后解析器读出B的JSON关系并创建填充数据的相应对象。通过在A上调用performBlock:将这些新对象添加到A. 因为解析器是循环的,解析B意味着解析C,这里也创建新对象并附加到B. 这一切都发生在MOC 3上的addBObject:

    • 解析(创建'A'对象并开始解析B)
      • 解析A(创建'B'对象,将它们附加到A并开始解析C)
        • 解析B(创建'C'对象,将它们附加到B)
          • 解析C(仅将数据存储在C对象中)

    在每次解析操作之后,我保存MOC 3并在mainThread上调度主MOC(2)的保存操作。由于performBlock:通知,MOC 1将异步自动保存。

    NSManagedObjectContextDidSave

    要释放我的内存占用,因为我现在不需要解析数据,我正在执行:

            if (parsed){
                NSError *error = nil;
                if (![managedObjectContext save:&error])
                    NSLog(@"Error while saving parsed data: %@", error);
            }else{
                // something went wrong, discard changes
                [managedObjectContext reset];
            }
    
            dispatch_async(dispatch_get_main_queue(), ^{                
                // save mainQueueManagedObjectContext
                [[HWOverallDataController sharedOverallDataController] saveMainThreadManagedObjectContext];
            });
    

    我刚解析的每个A。

    因为我需要解析大约10个A,它们都有大约10个B,它们都有大约10个C,所以生成了很多的managedObject。

    问题 - 工具

    一切正常。唯一的事情是:当我打开分配工具时,我看到未发布的A,B和C。我没有从他们的retainCounts或任何内容中获得任何有用的信息。 并且因为我的实际问题涉及更复杂的dataModel,所以生物对象成为严重的内存问题。 有人能弄清楚我做错了什么吗?使用正确的managedObject在其他managedObjectContexts上调用refreshObjects也不起作用。只有一个很难的[a.managedObjectContext refreshObject:a mergeChanges:NO]; 似乎有用,但后来我忽略了UI使用的生物对象。

    我试过的其他解决方案

    • 我尝试创建单向关系而不是双向关系。这会产生许多其他问题,导致核心数据不一致和奇怪的行为(例如悬空对象和核心数据生成1-n关系而不是n-n关系(因为反向关系未知)。

    • 我在任何对象上检索reset通知时尝试刷新每个已更改或插入的对象

    这两种“解决方案”(顺便说一下都不起作用)似乎也有些笨拙。这不应该是要走的路。应该有一种方法可以在不增加内存占用和保持UI流畅的情况下实现这一点吗?

    - CodeDemo

    http://cl.ly/133p073h2I0j

    - 进一步调查

    在mainContext中(在mainSave之后)刷新所有使用过的对象(这是繁琐的工作)后,对象的大小减少到48个字节。这表示对象都是故障,但内存中仍有一个指针。当我们有大约40.000个对象都出现故障时,内存中仍有1.920 MB,在重置persistentManagedObjectContext之前永远不会释放。这是我们不想做的事情,因为我们放弃了对任何托管对象的每个引用。

3 个答案:

答案 0 :(得分:5)

罗宾,

我有类似的问题,我解决的问题与你的不同。在您的情况下,您有第三个IMO冗余MOC,即父MOC。就我而言,我让两个MOC以旧学校的方式通过DidSave通知通过持久性商店协调员进行通信。新的面向块的API使这更加简单和健壮。这让我可以重置子MOC。虽然您从第三个MOC获得了性能优势,但它与我利用的SQLite行缓存相比并没有那么大的优势。你的路径消耗更多的内存。最后,我可以通过跟踪DidSave通知来修剪项目。

顺便说一下,您的MALLOC_TINYMALLOC_SMALL VM区域的大小也可能会大幅增加。我的尾随修剪算法让分配器可以更快地重用空间,从而延缓这些有问题区域的增长。根据我的经验,这些地区由于其庞大的居民记忆足迹而成为我的应用程序Retweever被杀害的主要原因。我怀疑你的应用遭遇了同样的命运。

当记忆警告到来时,我打电话给下面的片段:

[self.backgroundMOC performBlock: ^{ [self.backgroundMOC reset]; }];

[self.moc save];

[self.moc.registeredObjects trimObjects];

-[NSArray(DDGArray) trimObjects]只是通过一个数组并刷新对象,从而修剪它们。

总之,Core Data似乎为许多MOC中出现的项目实现了写入算法的副本。因此,你会以意想不到的方式保留东西。我专注于在导入后断开这些连接以最小化我的内存占用。由于SQLite行缓存,我的系统似乎可以很好地执行。

安德鲁

答案 1 :(得分:1)

对于您出于特定目的而保留的每个NSManagedObjectContext,您将累积NSManagedObject

的实例

NSManagedObjectContext只是一张划痕便条纸,您可以随意实例化并保存,如果您希望保留NSPersistentStore中的更改,然后再丢弃。

对于解析操作(第3层),尝试为op创建MOC,进行解析,保存MOC,然后将其丢弃。

感觉你至少有一层MOC被强引用太多了。

基本上问每个MOC的问题。 “为什么要保持这个物体及其相关的孩子活着”。

答案 2 :(得分:0)

我有一个非常相似的导入助手。

查看下面的代码并查看它是否对您有所帮助

__block NSUInteger i = 0;
NSArray *jsonArray = ...
for (NSDictionary *dataStucture in jsonArray)
{
    [managedObjectContext performBlock:^{
        @autoreleasepool {
            i++;
            A *a = (A*)[self newManagedObjectOfType:@"A" inManagedObjectContext:managedObjectContext];
            [self parseData:[dataStucture objectForKey:@"a"]
                 intoObject:a
     inManagedObjectContext:managedObjectContext];

            [managedObjectContext refreshObject:a
                                   mergeChanges:YES];
            if (i > 20) // Arbitrary number here
            {
                NSError *error = nil;
                [managedObjectContext save:&error];
                [managedObjectContext reset];
            }

            [managedObjectContext refreshObject:a
                                   mergeChanges:YES];

        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [self saveMainThreadManagedObjectContext];

            NSLog(@"DONE");
            // parsing is done, now you see that there are still
            // A's, B's and C's left in memory.
            // Every managedObjectContext is saved and no references are kept
            // to any A, B and C so they should be released. This is not true,
            // so a managedObjectContext is keeping a strong reference to these
            // objects.
        });
    }];
}