6

当我创建没有释放 CGImage 的 UIImage 实例时,我似乎遇到了内存管理问题。

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

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

当滚动图像时,内存使用量只会增加,直到应用程序终止,如仪器屏幕截图所示。注意图像 io 的高分配。

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

虚拟内存跟踪器

在下面的屏幕截图中,可以看到 GTGImageScrollerPageViews、UIImageViews 和 UIImages 正在被释放。请注意,这些编号在高 300 中存在 Transitory 对象。然而,CGImages 没有被发布,并且 CGImages 的数量处于高 400 和 0 Transitory。

显示分配的屏幕截图

分配

编辑:以前我一直在回收和重新使用 ScrollView 中的 GTGImageScrollerPageView 实例,这是此类滚动视图的常见模式。为了在尝试调试此问题时进行简化,我允许在将整个 GTGImageScrollerPageView 显示在 ScrollView 中后将其释放。正如您在上面的第二张图片中看到的那样,只有 4 个 GTGImageScrollerPageView 活生生的和 377 个暂时性的,还有 388 个 UIImageViews 和 389 个 UIIMages 被列为暂时性,因此看来 UIImageViews 和 UIImages 正在被很好地释放。

如果我使用 CGImageRelease 手动释放 CGImage(在下面的代码中注释掉),则释放 CGImage。我知道我不应该这样做,因为我不拥有 CGImage 但这对于验证这是发生问题的地方很有用。下面的屏幕截图显示了在 Instruments 中测试的相同代码,但 CGImageRelease 未注释。

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

虚拟内存使用

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

在此处输入图像描述

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

如果这是 CGImage 的一些系统缓存,那么它应该在引发内存警告时释放内存,但事实并非如此。内存只会继续增长。

是什么导致了内存的无限增长?

这是页面视图的代码

编辑:作为对评论的回应,我更新了代码以进一步简化,从而消除了诸如 ivars 和不需要演示问题的属性之类的干扰。问题保持不变,分析时结果相同。我还添加了也输出线程的 NSLog。我确实看到在 GTGImageScrollerPageView 上按预期调用了 dealloc,它始终是线程 #1,并且对 displayAspectThumbImage 的调用始终在线程 #1 上。

我真的不相信这里提供的代码有任何问题,Rob 的慷慨努力证实了这一点。其他原因导致了这种情况,但与加载和显示图像相关的所有代码都在这里;这里没有其他有效的代码。我能想到的唯一另一个值得注意的事情是 displayAspectThumbImage 方法是从 scrollViewDidScroll 和 scrollViewDidEndDecellating 方法调用的,但是在主线程上调用这些方法的事实应该排除由于在另一个线程上运行而导致的自动释放问题.

我已经检查并且没有启用 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

4

3 回答 3

4

很抱歉自己回答这个问题,但认为分享我的经验会很有用。

它原来是 UIImage 上的一个类别,它是我正在使用的第三方库的一部分。我不会为库命名,因为我已经使用最新版本进行了测试,并且问题不存在,因此命名库没有任何好处。

这特别奇怪,因为在泄漏的代码附近没有使用该库。它只在整个项目中的一个地方使用,但任何时候我使用 UIImage 似乎都会影响 UIImage 实例。仅在项目中存在此类别就足够了。这是一个真正的惊喜。

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

非常感谢 Rob 慷慨地帮助我解决这个问题,尽管他没有直接提供解决方案,但与他交谈确实有助于解决问题。很高兴知道外面有这么酷的人。

于 2013-06-13T19:12:03.227 回答
3

我使用您的代码做了一个简单的无限滚动,在滚动了近 100 张图像之后,正如人们所预料的那样,内存使用完全平静:

分配

虚拟机

查看您的源代码,我会推荐一些小东西(例如,我会放入aspectImageView私有类扩展而不是 .h,我假设您是pageIndex从调用例程设置的,但我会将其添加为displayAspectThumbImage,我会创建aspectImageView一个weak属性并相应地重构代码(例如,创建图像视图,将其添加为子视图,然后将weakimageview 属性设置为指向该对象)等),但这些都与您的问题没有直接关系。 .

最重要的是,您的问题不在于您与我们共享的代码。

于 2013-05-29T14:02:56.617 回答
0

尝试使用属性而不是 ivar。

@property (nonatomic, strong) UIImageView *aspectImageView;

于 2013-05-29T07:25:01.967 回答