内存警告和崩溃(ARC) – 如何确定它发生的原因?

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

我目前的项目是关于CoreGraphics很多 – 图表绘图,充满缩略图的视图等等。 我相信在使用手动内存pipe理时,除了几个僵尸之外,没有任何问题。但是到目前为止,应用程序每次尝试创build大量缩略图或重绘一些更复杂的图表时都会崩溃。

在使用仪器进行分析时,我可以看到驻留记忆以及肮脏记忆的非常高的价值。 堆分析显示相当惊人的不规则增长…

在绘制一些缩略图时,驻留内存会增长大约200 MB。 当绘制所有内容时,内存的回落几乎与绘制前相同。 但是,有了许多缩略图,常驻内存中的值高于400 MB ,显然会使应用程序崩溃。 我试图限制在同一时间绘制缩略图(NSOperationQueue和它的maxConcurrentOperationCount),但释放这么多的内存似乎需要多一点时间,它并没有真正解决这个问题。

现在,我的应用程序基本上不工作,因为真正的数据与许多复杂的图表=很多缩略图。

每个缩略图都是用我从这里得到的代码创build的:( 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时代码真的有问题吗?

另一个发生内存警告+崩溃的地方是任何视图的重绘过多。 它不需要很快,只是很多次。 内存堆积起来,直到它崩溃,我无法find真正的责任。 (我可以看到虚拟机跟踪器中增长的常驻/脏内存以及Allocations工具中的堆增长)

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

我已阅读过渡到ARC发行说明,bbum有关堆分析的文章,可能还有其他一些文章。 不同的堆分析有和没有弧? 我似乎无法做任何有用的输出 。

谢谢你的任何想法。

更新:(不要强迫每个人读所有的评论,并履行我的诺言)

仔细地通过我的代码并添加@autoreleasepool,它有什么意义,内存消耗降低。 最大的问题是从后台线程调用UIGraphicsBeginImageContext 。 修复之后(请参阅@Tammo Freese的回答了解详情),即将发生的释放不会导致应用程序崩溃。

我的第二次崩溃(由许多重绘相同的图表引起的),完全通过在我的绘图方法的末尾添加CGContextFlush(context)来解决。 对我感到羞耻。


任何人试图做类似的小警告: 使用OpenGL。 CoreGraphics不够快,为大图绘制animation,特别是不在iPad 3上(第一个与视网膜)

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

关于你的问题:

在您张贴的工具的屏幕截图中 ,可以看到分配尖峰。 到目前为止,在我遇到的大多数情况下,这些尖峰是由于自动释放的物体徘徊太久造成的。

  1. 你提到你使用NSOperationQueue 。 如果重写-[NSOperationQueue main] ,请确保将方法的全部内容包装在@autoreleasepool { ... } 。 一个autorelease池可能已经到位,但不能保证(即使有一个,它可能会比你想象的更长)。

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

  3. 你提到你使用NSOperationQueue 。 从iOS 4开始,在UIKit中绘制graphics上下文是线程安全的,但是如果文档是正确的, UIGraphicsBeginImageContext仍然只能在主线程上调用! 更新:文档现在声明,从iOS 4开始,函数可以从任何线程调用,到下面实际上是不必要的! 为了安全起见,使用CGBitmapContextCreate创build上下文,并使用CGBitmapContextCreate检索图像。 沿着这些线路的东西:

     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; 

所以,你没有做什么相对于内存pipe理(没有!)看起来不合适。 但是,你提到使用NSOperationQueue。 那些UIGraphics …调用被标记为不是线程安全的,但其他人已经声明他们是iOS 4(我无法find明确的答案,但回想一下这是事实。

无论如何,你不应该从多个线程调用这些类方法。 您可以创build一个串行调度队列并通过它来提供所有工作,以确保单线程使用。

这里缺less的东西当然是你在使用图像后对图像所做的事情。 它有可能以某种不明显的方式保留它们。 这里有一些技巧:

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

  • 你可以尝试添加一个dealloc到UIImage来做同样的事情。

  • 尝试使用最简单的设置(最less的图像等)来驱动您的应用程序,以便您可以validation图像及其所有者是否正在分配。

  • 当你想确定发布的东西时,将ivar或property设置为nil

去年夏天,我将一个100文件项目转换成了ARC,并且它的工作非常完美。 我已经将几个开源项目转换为ARC,并且当我错误地使用桥接时,只有一个问题。 技术是坚实的。

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

我曾经写了一个testing应用程序,在点击一个button后创build了许多自动释放对象。 我能够比操作释放内存的操作系统更快地(和创build对象)。 内存是慢慢增加,所以经过相当长的时间或简单地使用更大的对象,我肯定会崩溃的应用程序,并导致设备重启(看起来真的有效;))。 我检查了使用仪器,不幸的是影响了testing,使一切都变慢,但我想这也是不真实的,当不使用仪器。

另一方面,我正在研究一个比较复杂的大项目,并且有很多从代码创build的UI。 它也有很多string处理,没有人关心使用发布 – 当我上次检查时,有几千个autorelease调用。 所以在稍微广泛使用这个应用程序5分钟之后,它就会崩溃并重新启动设备。

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