【问题标题】:Huge memory consumption while parsing JSON and creating NSManagedObjects解析 JSON 和创建 NSManagedObjects 时消耗大量内存
【发布时间】:2013-08-30 06:31:02
【问题描述】:

我正在解析一个大小约为 53 MB 的 iPad 上的 JSON 文件。解析工作正常,我使用的是 Yajlparser,它是一个 SAX 解析器,并且设置如下:

    NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedAlways|NSDataReadingUncached error:&parseError];
    YAJLParser *parser = [[YAJLParser alloc] init];
    parser.delegate = self;
    [parser parse:data];

到目前为止一切正常,但 JSON 文件变大了,现在我突然在 iPad 2 上遇到内存警告。它收到 4 个内存警告,然后就崩溃了。在 iPad 3 上,它可以完美运行,没有任何内存警告。

我已经开始使用 Instruments 对其进行分析,并发现了很多 CFNumber 分配(几分钟后我停止了 Instruments,我之前运行它直到崩溃并且 CFNumber 的东西大约是 60 mb 或更多)。

打开 CFNumber 详细信息后,它显示了一个巨大的分配列表。其中一个向我展示了以下内容:

这里还有一个:

那我做错了什么?这个数字(例如最后一张图片中的 72.8%)代表什么?我正在使用 ARC,所以我没有做任何 Release 或 Retain 或其他任何事情。

感谢您的帮助。 干杯

编辑:我已经在这里问过如何解析如此大的文件的问题:iPad - Parsing an extremely huge json - File (between 50 and 100 mb)
所以解析本身似乎没问题。

【问题讨论】:

  • 这个数字意味着这部分代码导致您面临的主要问题的可能性为 72.8%。 (除了少数之外,它大部分时间都指向正确的方向)。仅仅因为您使用 ARC 并不意味着 100% 无错误/无泄漏代码。例如,循环引用仍然会导致代码泄漏。尝试运行内存分析器,看看您的“虚拟内存”是否变得太大。请发布您的反馈
  • 据我了解,CoreFoundation 确实会尽量减少对象实例的数量,例如数字和字符串。属性“kundennr”是否被定义为“副本”?看起来您可能会在每次分配该属性和“currentWarengruppeVK”时进行复制。这将抵消 CoreFoundation 提供的内置效率。
  • YAJL 解析器的一个潜在问题是它为原始 JSON 值(String、True、False、Number、Null)传递 NSObject。也就是说,它必须在内部为此分配 NSObjects。很可能,这些对象也将被放入自动释放池中。更好的方法不会分配任何东西来将 JSON 原始值传递给解析器的委托。通常(例如创建 CD 托管对象),这也是不必要的。无论如何,CD 托管对象都会为其属性创建副本。简而言之:IMO,YAJL 似乎不适合您的问题。
  • 一个具有潜在巨大治疗效果的小提示:不要将 NSNumber 用于“Kundennummer”。如果您的 Kundennummer" 不适合整数或包含非数字字符,您将遇到严重的麻烦。NSNumber 不会警告您!最好使用字符串。
  • @Dan_Gabicoware,不,kundennr 是这样定义的@property (nonatomic, retain) NSNumber * kundennr;,它是一个由 Xcode(NSManagedObject 子类)创建的实体。

标签: ios objective-c json memory-management didreceivememorywarning


【解决方案1】:

请参阅Efficiently Importing Data 上的 Apple 核心数据文档,尤其是“减少峰值内存占用”。

您需要确保一次没有太多新实体在内存中,这包括在解析数据时定期保存和重置上下文,以及使用好自动释放池。

一般的 sudo 代码是这样的:

while (there is new data) {
    @autoreleasepool {
        importAnItem();
        if (we have imported more than 100 items) {
            [context save:...];
            [context reset];
        }
    }
}

所以基本上,在主循环或解析代码周围放置一个自动释放池。计算您创建了多少 NSManagedObject 实例,并定期保存和重置托管对象上下文以将这些实例从内存中清除。这应该可以减少您的内存占用。数字 100 是任意的,您可能想尝试不同的值。

由于您要保存每个批次的上下文,因此您可能需要导入商店的临时副本,以防出现问题并导致部分导入。一切都完成后,您可以覆盖原始存储。

【讨论】:

  • 感谢您的回答。但是每次保存后我都在做[context reset]。我在 dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{} 中有主循环,但我没有使用 @autoreleasepool-thingy,因为我认为在使用 ARC 时我不需要关心它。
  • 尝试使用@autoreleasepool 将代码封装在主循环中(在循环内部,而不是在外部)。
  • 在 GCD 中使用 @autoreleasepool
  • 您的[NSNumber numberWithInteger:...] 行似乎导致了内存峰值,因此它可能向自动释放池中添加了许多数字,这些数字在 GCD 块完成之前不会被耗尽。因此,您需要在主循环体​​中的代码周围放置自己的自动释放池,以保持该池倒计时。
  • @gasparuff:“但我没有使用 @autoreleasepool-thingy,因为我认为在使用 ARC 时我不需要关心它。” ARC 并没有消除管理自动释放池生命周期的需要。它只是在需要时为您插入对-autorelease 的调用。
【解决方案2】:

在一定数量的插入操作后尝试使用[self.managedObjectContext refreshObject:obj refreshChanges:NO]。这会将 NSManagedObjects 变成故障并释放一些内存。

Apple Docs on provided methods

【讨论】:

  • 我试过这个,不幸的是这并没有真正帮助。它是[self.managedObjectContext refreshObject:obj mergeChanges:NO] :-)
猜你喜欢
  • 2011-02-20
  • 2012-11-14
  • 1970-01-01
  • 2022-01-16
  • 2014-01-04
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多