Кэширование UIImage с каталогами Xcode Asset

Мы все знаем о таинственном скрытом кэширующем механизме метода UIImage imageNamed:. В Apple Справочник класса UIImage в нем говорится:

В ситуациях с низкой памятью данные изображения могут быть очищены от объекта UIImage, чтобы освободить память в системе. Это поведение очистки влияет только на данные изображения, хранящиеся внутри объекта UIImage, а не на сам объект. При попытке нарисовать изображение, данные которого были очищены, объект изображения автоматически перезагружает данные из его исходного файла. Однако этот дополнительный шаг загрузки может привести к небольшому штрафу за производительность.

Фактически, данные изображения не будут "очищены от объекта UIImage, чтобы освободить память в системе", как показывает документация. Вместо этого приложение получает предупреждения о памяти до тех пор, пока оно не перестанет "из-за давления памяти".
РЕДАКТИРОВАТЬ: При использовании обычных ссылок на файлы изображений в вашем проекте Xcode кеширование UIImage отлично работает. Это просто, когда вы переходите к каталогам активов, которые не выпускаются в памяти.

Я реализовал UIScrollView с несколькими UIImageViews для прокрутки длинного списка изображений. При прокрутке следующие изображения загружаются и присваиваются свойству UIImageView image, удаляя сильную ссылку на ранее сохраненный UIImage.

Из-за механизма кэширования imageNamed: у меня быстро заканчивается память, и приложение завершается выделенной памятью объемом 170 МБ.

Конечно, существует множество интересных решений для реализации пользовательских механизмов кэширования, включая переопределение метода класса imageNamed: в категории. Часто вместо этого используется метод класса imageWithContentOfFile:, который не кэширует данные изображения, как это было предложено разработчиками Apple на WWDC 2011.

Эти решения отлично подходят для обычных файлов изображений, хотя вам нужно получить расширение пути и файла, которое не так элегантно, как мне бы хотелось.

Я использую новые Asset Catalogs, представленные в Xcode 5, однако, чтобы использовать механизмы условной загрузки изображений в зависимости от устройства и эффективного хранения файлов изображений. На данный момент, похоже, нет прямого способа загрузки изображения из каталога активов без использования imageNamed:, если только я не вижу очевидного решения.

Вы, ребята, вычислили механизм кэширования UIImage с каталогами ресурсов?

Я хотел бы реализовать категорию в UIImage, подобную следующей:

static NSCache *_cache = nil;

@implementation UIImage (Caching)

+ (UIImage *)cachedImageNamed:(NSString *)name {
    if (!_cache) _cache = [[NSCache alloc] init];

    if (![_cache objectForKey:name]) {
        UIImage *image = ???; // load image from Asset Catalog without internal caching mechanism
        [_cache setObject:image forKey:name];
    }

    return [_cache objectForKey:name];
}

+ (void)emptyCache {
    [_cache removeAllObjects];
}

@end

Еще лучше было бы получить больше контроля над внутренним кешем UIImage и возможностью очистки данных изображения в условиях низкой памяти, как описано в документации при использовании Каталогов активов.

Спасибо за чтение, и я с нетерпением жду ваших идей!

Ответ 1

ОБНОВЛЕНИЕ: Выселение кеша работает штрафы (по крайней мере, начиная с iOS 8.3).

Я столкнулся с той же проблемой (iOS 7.1.1), и я вроде как это @Lukas может быть прав

Существует высокая вероятность того, что ошибка не в Apple... кешировании, а в вашем.. коде.

Поэтому я написал очень простое тестовое приложение (см. полный источник ниже), где я все еще вижу проблему. Если вы видите что-то неправильно, сообщите мне об этом. Я знаю, что это действительно зависит от размеров изображения. Я вижу только проблему на iPad Retina.

  @interface ViewController ()

  @property (nonatomic, strong) UIImageView *imageView;
  @property (nonatomic, strong) NSArray *imageArray;
  @property (nonatomic) NSUInteger   counter;

  @end

  @implementation ViewController

  - (void)viewDidLoad
  {
      [super viewDidLoad];

      self.imageArray = @[@"img1", ...  , @"img568"];
      self.counter = 0;

      UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
      self.imageView = [[UIImageView alloc] initWithImage: image];
      [self.view addSubview: self.imageView];

      [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:1];
  }

  - (void)didReceiveMemoryWarning
  {
      [super didReceiveMemoryWarning];
      NSLog(@"WARN: %s", __PRETTY_FUNCTION__);
  }


  - (void)loadNextImage{

      self.counter++;
      if (self.counter < [self.imageArray count])
      {
          NSLog(@"INFO: %s - %lu - %@",
                __PRETTY_FUNCTION__,
                (unsigned long)self.counter,
                [self.imageArray objectAtIndex:self.counter]);
          UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
          self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
          [self.imageView setImage:image];

          [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:0.2];
      } else
      {
          NSLog(@"INFO: %s %@", __PRETTY_FUNCTION__, @"finished");
          [self.imageView removeFromSuperview];
      }
  }
  @end

Внедрение на месте

Я написал некоторый код для сохранения ресурса изображения, но загрузил его с помощью imageWithData: или imageWithContentsOfFile: использовать xcassets без imageNamed для предотвращения проблем с памятью?