Кэширование 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 для предотвращения проблем с памятью?