Память UIImageView не восстанавливается (долго и подробно) - PullRequest
1 голос
/ 06 ноября 2010

У меня есть UIView, который содержит около 80 UIImageViews. Когда я создаю новый набор из 80 UIImageViews каждый раз, когда UIView загружается с новыми изображениями, память из сброшенных 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

Прежде чем продолжить, я должен отметить, что я наблюдал за отличной сессией 104 «Разработка приложений с представлениями прокрутки» из WWDC 2010, и я знаю все о том, как и зачем перерабатывать UIImageViews.

Тем не менее, сессия предполагает, что цель рециркуляции состоит в том, чтобы избежать выделения UIImageView для каждого возможного изображения, поскольку когда-либо видны только два, а третий нужен для анимации. Чего они не упоминают, так это того, что память, используемая этими тремя UIImageViews, вообще исчезает при выгрузке всего представления.

Хорошо, теперь вопрос: есть ли способ вернуть память из подпредставлений UIImageView, если удаление их из суперпредставления не делает этого?

(Это не проблема кэширования самих UIImages, поскольку оба примера используют один и тот же набор UIImages.)

Идеи кому-нибудь?

Примечание. Меня также беспокоит увеличение памяти даже при утилизации. Это в конечном итоге убьет мое приложение, так как я показываю слайд-шоу с тысячами изображений, которые будут работать часами.


вклад Тии (см. Ниже) не является ответом. (Как примечание, я не изменял массив; я изменял состояние, для которого массив является моментальным снимком.) Чтобы доказать это, я переписал цикл удаления-удаления вида так:

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 Ответ

1 голос
/ 06 ноября 2010

Я бы сомневался в двух моментах здесь:

  1. Вы модифицируете свой массив в цикле. Если вы собираетесь удалить все подпредставления, у вас должна быть копия массива и итерация вместо нее, например. используя [self.view.subviews copy].
  2. removeFromSuperview может делать autorelease, а не release, поэтому в этом случае память не будет немедленно восстановлена.

изм:

Здесь есть одна утечка памяти:

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

возможно, вы забыли отключить просмотр изображения.

Еще один момент: вам нужно освободить или удалить все элементы из вашего imageViews, а также, если вы хотите восстановить память.

...