内存警告和崩溃(ARC) - 如何识别它发生的原因?

时间:2012-07-20 11:59:52

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

我最近开始使用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做任何有用的事情。

感谢您的任何想法。

更新:(不强迫所有人阅读所有评论并履行我的承诺)

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

我的第二次崩溃(由许多重绘同一图表引起)在我的绘图方法结束时添加CGContextFlush(context)完全解决了。对我感到羞耻。


对于试图做类似事情的人来说,这是一个小小的警告:使用OpenGL。 CoreGraphics不够快,无法为大图画制作动画,特别是在iPad 3上没有。(第一个有视网膜)

3 个答案:

答案 0 :(得分:18)

回答您的问题:使用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;
    

答案 1 :(得分:2)

所以,相对于内存管理而言,你没有做任何事情(没有!)看起来不合适。但是,您提到使用NSOperationQueue。那些UIGraphics ...调用被标记为不是线程安全的,但是其他人已经声明它们是iOS 4(我无法找到明确的答案,但请回想一下这是真的。

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

这里缺少的是使用它们后对图像的处理方式。你可能会以某种不明显的方式保留它们。以下是一些技巧:

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

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

  • 尝试使用最简单的设置驱动您的应用 - 最少的图像等 - 这样您就可以验证图像及其所有者实际上已经被解除了。

  • 如果要确保释放某些内容,请将ivar或属性设置为nil

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

答案 2 :(得分:2)

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

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

另一方面,我正在开发一个相当复杂的大型项目,并且有许多用代码创建的UI。它也有很多字符串处理,没有人关心使用发布 - 我上次检查时有几千个自动释放呼叫。因此,在稍微广泛使用此应用程序5分钟后,它崩溃并重新启动设备。

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