【问题标题】:Memory warning and crash (ARC) - how to identify why it's happening?内存警告和崩溃 (ARC) - 如何确定它发生的原因?
【发布时间】:2012-07-19 16:56:11
【问题描述】:

我最近开始使用 ARC,从那时起我就把每一个内存问题都归咎于它。 :) 也许,你可以帮助我更好地理解我做错了什么。

我目前的项目主要是关于 CoreGraphics - 图表绘制、带有缩略图的视图等等。我相信使用手动内存管理不会有问题,除了一些僵尸......但到目前为止,每次我尝试创建大量缩略图或重绘更复杂的图表时,应用程序都会崩溃。

在使用 Instruments 进行分析时,我可以在常驻内存和脏内存中看到非常高的价值。堆分析显示相当惊人的不规则增长...

仅绘制几个缩略图时,常驻内存会增长约 200 MB。绘制完所有内容后,内存会回落到与绘制前几乎相同的值。但是,对于很多缩略图,常驻内存中的值高于400 MB,这显然会使应用程序崩溃。我试图限制同时绘制的缩略图数量(NSOperationQueue 及其 maxConcurrentOperationCount),但由于释放这么多内存似乎需要更多时间,它并没有真正解决问题。

现在我的应用程序基本上无法正常工作,因为真实数据可以处理大量复杂的图表 = 大量缩略图。

每个缩略图都是用我从这里得到的代码创建的:(UIImage 上的类别)

+ (void)beginImageContextWithSize:(CGSize)size
{
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
        } else {
            UIGraphicsBeginImageContext(size);
        }
    } else {
        UIGraphicsBeginImageContext(size);
    }
}

+ (void)endImageContext
{
    UIGraphicsEndImageContext();
}

+ (UIImage*)imageFromView:(UIView*)view
{
    [self beginImageContextWithSize:[view bounds].size];
    BOOL hidden = [view isHidden];
    [view setHidden:NO];
    [[view layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    [view setHidden:hidden];
    return image;
}

+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
    UIImage *image = [self imageFromView:view];
    if ([view bounds].size.width != newSize.width ||
        [view bounds].size.height != newSize.height) {
        image = [self imageWithImage:image scaledToSize:newSize];
    }
    return image;
}

+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
    [self beginImageContextWithSize:newSize];
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    return newImage;
}

在使用 ARC 时,是否有其他方法不会占用太多内存或代码确实有问题?

发生内存警告+崩溃的另一个地方是任何视图的重绘过多。它不需要很快,只要多次。内存堆积起来,直到它崩溃,我无法找到任何真正对它负责的东西。 (我可以在 VM Tracker 中看到不断增长的常驻/脏内存和在 Allocations 工具中的堆增长)

我的问题基本上是:如何找到它为什么会发生?我的理解是当给定对象没有所有者时,它会尽快发布。我对代码的检查表明,很多对象根本没有被释放,即使我没有看到任何发生的原因。我不知道任何保留周期...

我已经阅读了过渡到 ARC 发行说明、bbum 的关于堆分析的文章以及可能还有其他十几个文章。使用和不使用 ARC 的堆分析有何不同?我似乎无法用它的output 做任何有用的事情。

感谢您的任何想法。

更新:(不强迫每个人阅读所有的 cmets 并遵守我的承诺)

通过仔细检查我的代码并添加@autoreleasepool,它有任何意义,内存消耗降低了。最大的问题是从后台线程调用UIGraphicsBeginImageContext。修复后(详见@Tammo Freese 的回答)释放很快就不会导致应用崩溃。

我的第二次崩溃(由多次重绘同一图表引起)通过在我的绘图方法末尾添加CGContextFlush(context) 完全解决。真丢人。


对任何尝试做类似事情的人的一个小警告:使用 OpenGL。CoreGraphics 对于大型绘图的动画效果不够快,尤其是在 iPad 3 上。(第一个使用视网膜)

【问题讨论】:

  • 编译器不会自动管理 Core Foundation 对象的生命周期;您必须按照 Core Foundation 内存管理规则的规定调用 CFRetain 和 CFRelease(或相应的特定于类型的变体)(请参阅 Core Foundation 的内存管理编程指南)。请参阅developer.apple.com/library/ios/#releasenotes/ObjectiveC/… 也可以在此处找到有关 ARC、免费桥接和核心图形的优秀帖子:stackoverflow.com/a/7800359/312312
  • @Lefteris 谢谢,我已经阅读了 Apple 的文章并观看了您链接的答案中提到的视频,但是当我画画时,我并没有真正保留任何东西,只是简单的 CGContextAddLineToPointCGContextAddRect,... 返回 void。我应该释放从CGContextRef context = UIGraphicsGetCurrentContext(); 获得的上下文吗?不过,我会调查一下。

标签: objective-c ios core-graphics automatic-ref-counting


【解决方案1】:

回答您的问题:使用 ARC 识别内存警告和崩溃问题基本上与之前使用手动保留释放 (MRR) 一样。 ARC 使用 retainreleaseautorelease,就像 MRR 一样,它只为您插入调用,并且进行了一些优化,在某些情况下甚至可以降低内存消耗。

关于你的问题:

screenshot of Instruments you posted 中,存在可见的分配峰值。到目前为止,在我遇到的大多数情况下,这些峰值都是由自动释放的对象悬停太久造成的。

  1. 你提到你使用NSOperationQueue。如果覆盖-[NSOperationQueue main],请确保将方法的全部内容包装在@autoreleasepool { ... } 中。自动释放池可能已经存在,但不能保证(即使有,它的存在时间也可能比您想象的要长)。

  2. 如果 1. 没有帮助,并且您有一个处理图像的循环,请将循环的内部部分包裹在 @autoreleasepool { ... } 中,以便立即清理临时对象。

  3. 你提到你使用NSOperationQueue。从 iOS 4 开始,在 UIKit 中绘制到图形上下文是线程安全的,但如果文档正确,UIGraphicsBeginImageContext 仍然应该只在主线程上调用! 更新:文档现在声明,从 iOS 4 开始,可以从任何线程调用该函数,实际上没有必要执行以下操作! 为了安全起见,请使用 CGBitmapContextCreate 创建上下文并检索带有CGBitmapContextCreateImage 的图像。大致如下:

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    
    // draw to the context here
    
    CGImageRef newCGImage = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    UIImage *result = [UIImage imageWithCGImage:newCGImage scale:scale orientation: UIImageOrientationUp];
    CGImageRelease(newCGImage);
    
    return result;
    

【讨论】:

  • 谢谢!似乎是内存问题的可能解决方案,但 CGBitmapContextCreate 总是返回 nil ,我还没有发现为什么......? @autoreleasepool 就在我的代码周围。 :)(你提到的所有地方)
  • @autoreleasepools 的帮助,还是您仍然看到内存问题?对于CGBitmapContextCreate,如果无法创建上下文,则返回NULL,这可能是由错误的参数引起的。试试CGBitmapContextCreate(NULL, 640, 480, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast)是否适合你,它应该返回非NULL
  • CGBitmapContextCreate 返回NULL 时,请检查您使用的参数是否有效。在 OS X 上,检查控制台是否有关于 CGBitmapContextCreate 的错误参数的消息
  • 另外,对于缺少 @autoreleasepool,您将在程序输出中看到“没有 NSAutoreleasePool,只是泄漏...”。查找以确保。
  • @TammoFreese 哇!使用的内存要少得多,似乎绘制的视图几乎立即被释放!我现在无法使用 iDevice,但看起来很有希望。我会在接下来的几个小时内检查它。
【解决方案2】:

这不是您问题的答案,但早在引入 ARC 之前,我就试图解决类似的问题。 最近我正在开发一个应用程序,它在内存中缓存图像并在收到内存警告后将它们全部释放。只要我以正常速度使用该应用程序(没有疯狂敲击),它就可以正常工作。但是当我开始生成大量事件并开始加载许多图像时,应用程序无法获得内存警告并且崩溃了。

我曾经编写过一个测试应用程序,它在点击一个按钮后会创建许多自动释放的对象。我能够比操作系统释放内存的速度更快(并创建对象)。内存在慢慢增加,所以在很长一段时间后或只是使用更大的对象后,我肯定会使应用程序崩溃并导致设备重新启动(看起来真的很有效;))。我检查了使用 Instruments 不幸地影响了测试并使一切变慢,但我想在不使用 Instruments 时也是如此。

另外一次,我正在处理一个更大的项目,该项目非常复杂,并且有很多由代码创建的 UI。它也有很多字符串处理,没有人关心使用 release - 我上次检查时有几千个 autorelease 调用。因此,在稍微广泛使用此应用程序 5 分钟后,它会崩溃并重新启动设备。

如果我是正确的,那么负责实际释放内存的操作系统/逻辑不够快或没有足够高的优先级来避免应用程序在执行大量内存操作时崩溃。我从未证实过这些怀疑,除了简单地减少分配的内存之外,我不知道如何解决这个问题。

【讨论】:

  • 我还设法通过疯狂地敲击来重置设备! :) 我设法降低了一点内存要求,让我慢慢地运行它。还不足以真正使用——我猜是设计不好的迹象。糟糕的是,我从来没有意识到我实际上可以使用所有可用的内存。如果我以前在这里读过像你这样的答案就好了。 :) 谢谢。
【解决方案3】:

因此,您在内存管理方面所做的一切(没有!)看起来不合适。但是,您提到使用 NSOperationQueue。那些 UIGraphics... 调用被标记为不是线程安全的,但其他人表示它们是 iOS 4 的(我找不到明确的答案,但请记住这是真的。

在任何情况下,您都不应该从多个线程调用这些类方法。您可以创建一个串行调度队列并通过它提供所有工作以确保单线程使用。

当然,这里缺少的是您在使用图像后如何处理它们。您可能以某种不明显的方式保留它们。这里有一些技巧:

  • 在任何使用大量图像的类中,添加一个仅记录其名称和一些标识符的 dealloc() 方法。

  • 您可以尝试向 UIImage 添加一个 dealloc 来执行相同的操作。

  • 尝试使用最简单的设置(最少的图像等)来驱动您的应用程序,这样您就可以验证图像及其所有者实际上是否已被解除分配。

  • 当您想确保发布某些内容时,请将 ivar 或属性设置为 nil

去年夏天,我将一个 100 个文件的项目转换为 ARC,它开箱即用地完美运行。当我不正确地使用桥接时,我也将几个开源项目转换为 ARC,但只有一个问题。该技术坚如磐石。

【讨论】:

  • 感谢您的建议!我明天试试。我还可以确认问题不在 ARC 中 - 我已经重写了整个应用程序以手动管理内存并猜测......一切都完全相同。 (至少从这里讨论的一个问题来看)
猜你喜欢
  • 2013-09-07
  • 2011-10-15
  • 2011-01-23
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 2012-03-03
  • 2013-04-27
相关资源
最近更新 更多