Iphone UIImageView内存未恢复(长且详细)

Iphone UIImageView内存未恢复(长且详细),iphone,iphone-sdk-3.0,memory-leaks,ios4,uiimageview,Iphone,Iphone Sdk 3.0,Memory Leaks,Ios4,Uiimageview,我有一个UIView,它包含大约80个UIImageView。每当UIView加载新图像时,我创建一组80个UIImageView的新集合时,丢弃的UIView中的内存不会被恢复。(解决方案是继续回收同一组UIImageView,但我担心的是内存不会被回收,因为最终我确实希望从回收的UIImageView中回收内存。) 下面是我的示例代码: #import "ImageMemViewController.h" #import <mach/mach.h> static NSArray

我有一个UIView,它包含大约80个UIImageView。每当UIView加载新图像时,我创建一组80个UIImageView的新集合时,丢弃的UIView中的内存不会被恢复。(解决方案是继续回收同一组UIImageView,但我担心的是内存不会被回收,因为最终我确实希望从回收的UIImageView中回收内存。)

下面是我的示例代码:

#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
在继续之前,我应该注意到我已经观看了WWDC 2010的优秀课程104“使用滚动视图设计应用程序”,我知道如何以及为什么要回收UIImageView

但是,本课程建议循环使用的目的是避免为每个可能的图像分配UIImageView,因为只有两个图像被看到,动画需要第三个图像。他们没有提到的是,在卸载整个视图时,这三个UIImageView使用的内存是否会消失

好的,现在来问一个问题:如果从UIImageView子视图中删除它们不起作用,有没有办法从UIImageView子视图中恢复内存

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

有人吗

注意:我也关心内存的增加,即使回收。这将最终扼杀我的应用程序,因为我正在播放一个包含数千张图片的幻灯片,这些图片将持续运行数小时


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];
UIImageView*视图[200];//注:C数组;不影响保留计数。
int j=0;
用于(在self.view.subview中的UIImageView*v)
视图[j++]=v;
对于(int i=0;i

结果完全一样。(内存增益量是相同的。)

我怀疑这里有两点:

  • 您正在修改循环中的数组。如果要删除所有子视图,则应具有数组的副本并对其进行迭代,例如使用
    [self.view.subviews copy]
  • removeFromSuperview
    可能会执行
    autorelease
    而不是
    release
    ,因此在这种情况下不会立即回收内存
  • 编辑:

    这里有一个内存泄漏:

    [imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
    
    也许你忘了释放图像视图


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

    我怀疑有两点:

  • 您正在修改循环中的数组。如果要删除所有子视图,则应具有数组的副本并对其进行迭代,例如使用
    [self.view.subviews copy]
  • removeFromSuperview
    可能会执行
    autorelease
    而不是
    release
    ,因此在这种情况下不会立即回收内存
  • 编辑:

    这里有一个内存泄漏:

    [imageViews addObject:[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]];
    
    也许你忘了释放图像视图


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

    (上面对tia答案的回答是对我最初问题的补充。)UIimageview的分配不在循环中,也不考虑内存的增加。请注意,这两种选择都是通用的。我明白了。在每次循环后,如何排空自动释放池?用alloc/init包围showWithNewImages函数中的代码,然后释放NSAutoReleasePool,但没有效果。我尝试将
    reportMemory
    作为实例方法,并将
    [自执行选择器:@selector(reportMemory)with Object:nil afterDelay:1]一切结束后,我看到内存使用率下降到17MB。我不确定这意味着什么,但我希望这能给你一些提示。(对tia答案的回答在上面是对我最初问题的补充。)UIImageViews的分配不在循环中,也不能解释内存的增加。请注意,这两种选择都是通用的。我明白了。在每次循环后,如何排空自动释放池?用alloc/init包围showWithNewImages函数中的代码,然后释放NSAutoReleasePool,但没有效果。我尝试将
    reportMemory
    作为实例方法,并将
    [自执行选择器:@selector(reportMemory)with Object:nil afterDelay:1]一切结束后,我看到内存使用率下降到17MB。我不知道这是什么意思,但我希望这能给你一些提示。