Marc Rochkind
Marc Rochkind

Reputation: 3740

UIImageView memory not being recovered (long and detailed)

I have a UIView that contains about 80 UIImageViews. When I create a new set of 80 UIImageViews each time the UIView is loaded with new images, memory from the discarded UIViews is not being recovered. (The solution is to keep recycling the same set of UIImageViews, but my concern is that the memory is not being recovered, because eventually I do want the memory even from the recycled UIImageViews back.)

Here's my example code:

#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

The two important methods are showWithNewImages and showWithRecycledImages. The former allocates new UIImageViews each time it's called, and the latter just recycles the same set if UIImageViews.

Here's the output:

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

Before continuing, I should note that I have watched the excellent Session 104, "Designing Apps with Scroll Views" from WWDC 2010, I and know all about how and why to recycle UIImageViews.

However, the session suggests that the purpose of recycling is to avoid allocating a UIImageView for every possible image, since only two are ever seen and a third one is needed for animation. What they don't mention is whether the memory used by those three UIImageViews even goes away when the entire view is unloaded.

OK, now for the question: Is there any way to get the memory back from the UIImageView subviews if removing them from their superview doesn't do it?

(This is not an issue of caching of the UIImages themselves, as both examples use the same set of UIImages.)

Ideas anyone?

Note: I am also concerned with the memory gain even when recycling. This will eventually kill my app, since I am showing a slide show of thousands of images that will run for hours.


tia's contribution (see below) is not the answer. (As a side note, I was not modifying the array; I was modifying the state of which the array is a snapshot.) To prove this, I rewrote the subview-removal loop to be this:

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];

with exactly the same results. (Memory gain amount is identical.)

Upvotes: 1

Views: 572

Answers (1)

tia
tia

Reputation: 9698

I would doubt for two points here:

  1. You are modifying your array in the loop. If you are going to remove all subviews, you should have a copy of the array and iterate it instead e.g. using [self.view.subviews copy].
  2. removeFromSuperview might do autorelease rather than release, so the memory will not be reclaimed immediately in that case.

edited:

There is one memory leak here:

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

maybe you forgot to release the image view.

One more point is, you need to release or remove all items from your imageViews as well if you want to recover the memory.

Upvotes: 1

Related Questions