UIImageView内存无法恢复(冗长而详细)

时间:2010-11-06 18:28:48

标签: iphone iphone-sdk-3.0 memory-leaks ios4 uiimageview

我有一个包含大约80个UIImageViews的UIView。每当UIView加载新图像时,我创建一组新的80个UIImageViews时,丢弃的UIViews的内存不会被恢复。 (解决方案是继续回收同一组UIImageViews,但我担心的是内存没有恢复,因为最终我确实想要内存甚至从回收的UIImageViews回来。)

这是我的示例代码:

#import "ImageMemViewController.h"
#import <mach/mach.h>

static NSArray *paths;
static NSMutableArray *images;
static NSMutableArray *imageViews;

static vm_size_t report_memory(void)
{
 struct task_basic_info info;
 mach_msg_type_number_t size = sizeof(info);
 kern_return_t kerr = task_info(mach_task_self(),
   TASK_BASIC_INFO,
   (task_info_t)&info,
   &size);
 if (kerr == KERN_SUCCESS) {
  NSLog(@"Memory used: %f MB", (float)info.resident_size / (1024 * 1024));
  return info.resident_size;
 }
 else
  NSLog(@"Error: %s", mach_error_string(kerr));
 return 0;
}

@implementation ImageMemViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
 paths = [NSArray arrayWithObjects:
    @"MJR_20070519_0529.jpg",
    @"MJR_20070519_0537.jpg",
    @"MJR_20070519_0545.jpg",
    @"MJR_20070519_0559.jpg",
    nil];
 images = [[NSMutableArray alloc] initWithCapacity:paths.count];
 for (NSString *p in paths)
  [images addObject:[UIImage imageNamed:p]];
 imageViews = [[NSMutableArray alloc] initWithCapacity:100];
 for (int i = 0; i < 100; i++)
  [imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
}

- (void)showWithNewImages
{
 // Question: What can be done here to reclaim memory?
#if 0 // this was tried
 for (UIImageView *v in self.view.subviews) {
  v.image = nil;
  [v removeFromSuperview];
 }
#else // as was this -- no difference
 NSArray *subviews = self.view.subviews;
 for (int i = 0; i < subviews.count; i++) {
  UIImageView *v = [subviews objectAtIndex:i];
  v.image = nil;
  [v removeFromSuperview];
 }
#endif
 int n = 0;
 for (int x = 10; x < 700; x += 100)
  for (int y = 10; y < 1000; y += 100) {
   UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, 90, 90)];
   v.image = [images objectAtIndex:n % paths.count];
   n++;
   [self.view addSubview:v];
   [v release];
  }
}

- (void)showWithRecycledImages
{
 for (UIImageView *v in self.view.subviews) {
  v.image = nil;
  [v removeFromSuperview];
 }
 int n = 0;
 for (int x = 10; x < 700; x += 100)
  for (int y = 10; y < 1000; y += 100) {
   UIImageView *v = [imageViews objectAtIndex:n];
   v.frame = CGRectMake(x, y, 90, 90);
   v.image = [images objectAtIndex:n++ % paths.count];
   [self.view addSubview:v];
  }
}

- (void)viewDidAppear:(BOOL)animated
{
 vm_size_t start = report_memory();
 for (int i = 0; i < 1000; i++) {
  [self showWithRecycledImages];
  if (i % 250 == 0)
   report_memory();
 }
 NSLog(@"showWithRecycledImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));

 start = report_memory();
 for (int i = 0; i < 1000; i++) {
  [self showWithNewImages];
  if (i % 250 == 0)
   report_memory();
 }
 NSLog(@"showWithNewImages memory gain = %f MB", (float)(report_memory() - start) / (1024 * 1024));
} 

@end

两个重要的方法是showWithNewImages和showWithRecycledImages。前者在每次调用时都会分配新的UIImageViews,如果是UIImageViews,后者只会回收相同的集合。

这是输出:

2010-11-06 10:51:28.556 ImageMem[7285:207] Memory used: 12.496094 MB
2010-11-06 10:51:28.582 ImageMem[7285:207] Memory used: 12.636719 MB
2010-11-06 10:51:45.358 ImageMem[7285:207] Memory used: 12.898438 MB
2010-11-06 10:52:03.409 ImageMem[7285:207] Memory used: 13.218750 MB
2010-11-06 10:52:17.430 ImageMem[7285:207] Memory used: 13.546875 MB
2010-11-06 10:52:28.071 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.084 ImageMem[7285:207] showWithRecycledImages memory gain = 1.367188 MB
2010-11-06 10:52:28.087 ImageMem[7285:207] Memory used: 13.863281 MB
2010-11-06 10:52:28.153 ImageMem[7285:207] Memory used: 13.871094 MB
2010-11-06 10:52:40.717 ImageMem[7285:207] Memory used: 17.746094 MB
2010-11-06 10:52:46.620 ImageMem[7285:207] Memory used: 21.621094 MB
2010-11-06 10:52:49.684 ImageMem[7285:207] Memory used: 25.464844 MB
2010-11-06 10:52:52.772 ImageMem[7285:207] Memory used: 29.347656 MB
2010-11-06 10:52:52.775 ImageMem[7285:207] showWithNewImages memory gain = 15.484375 MB

在继续之前,我应该注意到我已经看过WWDC 2010中的优秀会议104,“使用滚动视图设计应用程序”,我知道如何以及为什么要回收UIImageViews。

然而,会议建议回收的目的是避免为每个可能的图像分配UIImageView,因为只有两个被看到,第三个是动画需要的。他们没有提到的是,当整个视图被卸载时,这三个UIImageViews使用的内存是否会消失。

好的,现在提出问题:如果从UIImageView子视图中删除内存,是否有任何方法可以从超级视图中删除内存?

(这不是UIImages本身的缓存问题,因为这两个例子都使用相同的UIImages集。)

想点什么?

注意:即使在回收时,我也关注内存增益。这最终会杀死我的应用程序,因为我正在显示数千张图片的幻灯片放映,这些图片将运行数小时。


tia的贡献(见下文)不是答案。 (作为旁注,我没有修改数组;我正在修改数组快照的状态。)为了证明这一点,我重写了子视图删除循环:

UIImageView *views[200]; // Note: C array; does not affect retain counts.
int j = 0;
for (UIImageView *v in self.view.subviews)
    views[j++] = v;
for (int i = 0; i < j; i++)
    [views[i] removeFromSuperview];

具有完全相同的结果。 (记忆增益量相同。)

1 个答案:

答案 0 :(得分:1)

我怀疑这里有两点:

  1. 您正在循环中修改数组。如果您要删除所有子视图,您应该拥有该数组的副本并重复它,例如使用[self.view.subviews copy]
  2. removeFromSuperview可能会autorelease而不是release,因此在这种情况下不会立即回收内存。
  3. 编辑:

    这里有一个内存泄漏:

    [imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
    

    也许您忘了发布图片视图。

    还有一点是,如果要恢复内存,还需要释放或删除imageViews中的所有项目。