UIImage被释放,但CGImage没有被释放。

7

当我创建UIImage实例时,似乎存在内存管理问题,CGImages没有被释放。

我有一个分页UIScrollView来滚动一系列的JPG图像。下面是我的整个类,它是分页滚动视图中的一个页面视图。

代码在主线程上运行。代码使用ARC。我尝试使用imageWithContentsOfFile:(返回一个自动释放的对象)和initWithContentsOfFile:(返回一个保留的对象)加载图像。我尝试了@autoreleasepool和使用performSelectorOnMainThread来确保代码正在主线程上运行,正如其他帖子中建议的那样。

当滚动图片时,内存使用量会不断增长,直到应用程序被终止,如仪器截图所示。请注意,高分配给图像io。

显示虚拟内存使用情况的屏幕截图

Virtual Memory Tracker

在以下截图中,可以看到GTGImageScrollerPageViews、UIImageViews和UIImages已被释放。请注意,这些数字高达300多个。但是CGImages没有被释放,CGImages的数量高达400多个,0个短暂对象。

显示分配的屏幕截图

Allocations

编辑:以前我一直在ScrollView中回收和重用GTGImageScrollerPageView实例,这是像这样的滚动视图的常见模式。为了简化调试此问题,我允许整个GTGImageScrollerPageView在显示在ScrollView后被释放。如上面的第二张图所示,只有4个GTGImageScrollerPageView存活,377个短暂对象,还有388个UIImageViews和389个UIIMages被列为短暂对象,因此似乎UIImageViews和UIImages已经被很好地释放了。

如果我手动使用CGImageRelease释放CGImage(在下面的代码中注释掉),则CGImages将被释放。我知道我不应该这样做,因为我不拥有CGImage,但这对于验证问题出现的位置非常有用。下面的截图显示了在使用CGImageRelease进行测试的相同代码,但是CGImageRelease未被注释掉。

使用CGImageRelease显示虚拟内存使用情况的屏幕截图

Virtual memory usage

使用CGImageRelease显示分配的屏幕截图

enter image description here

在使用CGImageRelease的分析输出中,可以看到正确数量的CGImage对象是Living和Transitory,并且内存不会无限增长。此外,在使用CGImageRelease时,应用程序在使用过程中不会崩溃。

如果这是CGImage的一些系统缓存,那么当内存警告发生时,它应该释放内存,但它没有。内存仍然持续增长。
是什么原因导致内存无限增长?
以下是页面视图的代码。
编辑:回应评论后,我已经更新了代码,进一步简化了代码。消除了不需要演示问题的实例变量和属性等干扰因素,但问题并没有改变。在分析时结果是相同的。我还添加了NSLog,它会输出线程。我确实看到了GTGImageScrollerPageView上的dealloc被调用,它总是线程#1,并且对displayAspectThumbImage的调用始终在线程#1上。
我真的不认为这里呈现的代码有任何问题,这得到了Rob的大力支持。其他事情导致了这个问题,但是所有与加载和显示图像相关的代码都在这里;这里没有其他代码。我唯一能想到的值得注意的另一件事是displayAspectThumbImage方法是从scrollviewDidScroll和scrollViewDidEndDecellerating滚动视图委托方法中调用的,但是由于这些方法在主线程上运行,所以应该排除由于在另一个线程上运行而造成的autorelease问题。
我已经检查过,NSZombies未启用,也没有正在增加我的内存使用。实际上,当调用CGImageRelease方法时,内存使用情况非常平稳,如上面的屏幕截图所示。
@implementation GTGImageScrollerPageView

- (void)dealloc {

    NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSThread currentThread]);

 }


  - (void)displayAspectThumbImage:(NSString *)path {

      NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSThread currentThread]);

      UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
      [self addSubview:imageView];

      UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
      [imageView setImage:image];

      //If uncommented CGImages are disposed correctly
      //CGImageRelease([imageView.image CGImage]);

}


@end

测试环境:

iOS 6.1.4

iOS 5.0.1


你说得没错,释放 CGImage 不是正确的解决方案。你展示了图片视图的创建过程,但是你有删除它吗?当滚动浏览图像时,你应该删除旧的图片视图,然后再添加新的。你这样做了吗? - Rob
嗨,Rob,我编辑了问题以使其更清晰,但您可以从分配视图的对象摘要中看到,只有4个GTGImageScrollerPageViews是活动的,而377个瞬态说明该对象正在被解除分配。UIImageViews和UIImages也是如此。 - Jet Basrawi
3个回答

4

非常抱歉要自己回答这个问题,但我认为分享我的经验可能会有所帮助。

事实证明,这是一个UIImage类别,属于我使用的第三方库。我不想点名该库,因为我已经测试了最新版本,问题并不存在,所以没有必要点名。

特别奇怪的是,该库与泄漏代码的任何地方都没有关联。它只在整个项目的一个地方使用,但似乎每次我使用UIImage时,它都会影响UIImage实例。它的存在就足够了。这真是个惊喜。

我解决这个问题的方法是首先在一个新项目中模拟场景,并发现那里的代码没有泄漏。然后我逐步迁移代码,当我移到类别时泄漏出现了。

非常感谢Rob慷慨的帮助,即使他没有直接提供解决方案,与他交流对解决问题非常有帮助。很高兴知道世界上有这样酷的人存在。


我们的项目也遇到了同样的内存问题,但是还没有找到解决方案。我们在项目中使用了多个图像库。我通过断点检查发现,这些方法都没有被调用过,但是内存并没有被释放。你介意分享一下你遇到的库的名称吗?这可能有助于我们移除它并进行检查。还有其他建议吗? - Anil Sivadas
1
同意。我遇到了类似的问题,这个“答案”没有帮助,因为它避免了一个重要的细节.. - Thomas Clowes

3
我使用了你的代码制作了一个简单的无限滚动器,翻阅了近100张图片后,内存使用情况如预期地完全平稳:

allocations

vm

通过查看你的源代码,我会建议一些小改进(例如,我会将aspectImageView放入私有类扩展而不是.h文件中,我假设你正在从调用程序设置pageIndex,但我会将其添加为displayAspectThumbImage的参数,我会将aspectImageView设置为weak属性并相应地重构代码(例如,创建图像视图,将其添加为子视图,然后将weakimageview属性设置为指向此对象),等等),但这些都与您在这里遇到的问题没有直接关系。

总之,您的问题不在于您与我们分享的代码。


嗨 Rob,感谢你在这方面的努力。我完全同意这里的代码没有问题。然而除了传递一个图像加载路径之外,加载和显示图像没有涉及到其他代码。我并不真正期望这里的代码是问题所在。我希望有人能够意识到一些系统行为或其他因素,这些因素可能会导致此问题的发生,这是我没有考虑到的。 - Jet Basrawi
@JetB,你是否看到了GTGImageScrollerPageViewdealloc被调用了?除了上面的代码之外,你是否有任何访问aspectImageView的代码?(顺便说一句,这就是我建议将该属性作为私有类扩展的原因,这样你就知道没有任何东西可能会干扰它。) - Rob
我在项目的其他地方遇到了类似的问题(而且仍然存在这个问题),那里有一个自定义图像选择器类型的滚动条。同样,没有明显的原因。我选择暂时忽略它,但是在这个图像滚动条功能上又出现了,现在不能再忽略了,因为想要尽快发布。再次强调,代码似乎没有任何问题,当我在测试项目中模拟时一切正常。 - Jet Basrawi
@JetB 我也假设您已经关闭了僵尸进程和其他内存诊断工具? - Rob
嗨,Rob,我确实看到了从页面视图中记录的dealloc。我将尝试您关于移动声明的建议。我过去也尝试过没有ImageView实例变量或属性,只允许对imageview的唯一引用是添加为子视图时创建的引用。也会检查僵尸进程。 - Jet Basrawi
显示剩余3条评论

0

尝试使用属性而不是实例变量。

@property (nonatomic, strong) UIImageView *aspectImageView;


嗨Alex,我已经尝试过使用属性而不是实例变量,并更新了问题中的代码来反映这一点。不幸的是,行为完全相同。尽管如此,我真的不明白为什么在这里使用强大的语义属性会有任何不同于使用实例变量。问题不是UIImageView没有被正确释放,UIImageView已被释放。问题在于UIImageView中的UIImage似乎没有释放它的CGImage。 - Jet Basrawi
你的UIImageViews释放在哪里?或者你是在GTGImageScrollerPageView的dealloc方法中检查的吗? 如果是,尝试将所有UIImage对象放入数组中,并使用[_myImagesArray removeAllObjects]清除它们。这应该可以解决问题。 - alexhajdu
嗨,Alex,我正在使用ARC,所以不应该显式释放UIImageViews。UIImageViews被释放了,显示在其中的UIImages也被释放了。即使我不在UIImageView上设置图像(只是加载UIimage并对其不做任何操作),同样的事情也会发生:UIImage被释放了,但其中的CGImage没有被释放。 - Jet Basrawi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接