Рассеивание изображения UIImage Имя: FUD

117

Изменить февраль 2014: обратите внимание, что этот вопрос относится к iOS 2.0! С тех пор требования к изображениям и их обработка сильно изменились. Retina увеличивает размер изображений и немного усложняет их загрузку. Благодаря встроенной поддержке изображений iPad и Retina вам непременно следует использовать ImageNamed в своем коде .

Я вижу, как много людей говорят, что imageNamedэто плохо, но такое же количество людей говорят, что производительность хорошая, особенно при рендеринге UITableViews. См. Этот вопрос SO, например, или эту статью на iPhoneDeveloperTips.com

UIImage«S imageNamedметод , используемый для утечки , так что лучше всего избегать , но была исправлена в последних версиях. Я хотел бы лучше понять алгоритм кэширования, чтобы принять обоснованное решение о том, где я могу доверить системе кэширование моих изображений, а где мне нужно пройти лишнюю милю и сделать это самому. Мое текущее базовое понимание, что это просто NSMutableDictionaryиз UIImagesссылки по имени файла. Он становится больше, а когда заканчивается память, становится намного меньше.

Например, кто-нибудь знает наверняка, что кеш изображений позади imageNamedне реагирует didReceiveMemoryWarning? Кажется маловероятным, что Apple этого не сделает.

Если у вас есть представление об алгоритме кеширования, опубликуйте его здесь.

Рог
источник
2
я тоже. Похоже, в SO не так много гениев, как я думал.
Rog
Похоже, вы потратили некоторое время на изучение этого. Проводили ли вы какие-либо эксперименты, которые показали бы какое-либо негативное влияние UIImage imageNamed с последней версией какао-touch? Создайте UITableView и добавьте много (много тысяч) строк и посмотрите, не снижается ли производительность, когда вы показываете разные изображения в каждой строке. Может быть, тогда люди смогут прокомментировать ваши выводы.
stefanB 01
На самом деле, я не проводил столько экспериментов. Я использую imageNamed, и хотя я еще не оптимизировал это приложение для памяти, у меня нет ничего, что я мог бы указать прямо на imageNamed как на потребителя памяти. Я указал здесь несколько сообщений в блогах с жалобами на imagedNamed, и я задал аналогичный вопрос на досках Apple, которые получили больше действий. Я перепечатаю резюме и сделаю ссылку здесь, когда у меня будет время и я буду чувствовать, что он получил ответы, которые он получит.
Rog

Ответы:

85

tldr: ImagedNamed в порядке. Он хорошо обрабатывает память. Используйте это и перестаньте беспокоиться.

Изменить ноябрь 2012 : обратите внимание, что этот вопрос относится к iOS 2.0! С тех пор требования к изображениям и их обработка сильно изменились. Retina увеличивает размер изображений и немного усложняет их загрузку. Благодаря встроенной поддержке изображений iPad и Retina вам непременно следует использовать ImageNamed в своем коде. А теперь для потомков:

Сестра нить на форумах Apple , Dev получил несколько лучше трафика. В частности, Ринсвинд добавил немного авторитета.

В iPhone OS 2.x есть проблемы, при которых кэш imageNamed: не очищается даже после предупреждения о памяти. В то же время + imageNamed: широко используется не для кеширования, а для удобства, что, вероятно, усугубило проблему больше, чем должно было быть.

предупреждая, что

Что касается скорости, то существует общее непонимание происходящего. Самая большая вещь, которую делает + imageNamed: - это декодирование данных изображения из исходного файла, что почти всегда значительно увеличивает размер данных (например, файл PNG размером с экран может занимать несколько десятков КБ при сжатии, но занимает более половины МБ. в распакованном виде - ширина * высота * 4). Напротив, + imageWithContentsOfFile: будет распаковывать это изображение каждый раз, когда требуются данные изображения. Как вы понимаете, если вам нужны данные изображения только один раз, вы ничего здесь не выиграете, за исключением того, что кешированная версия изображения будет висеть поблизости и, вероятно, дольше, чем вам нужно. Однако, если у вас есть большое изображение, которое вам нужно часто перерисовывать, тогда есть альтернативы, хотя я бы рекомендовал в первую очередь избегать перерисовки этого большого изображения :).

Что касается общего поведения кеша, он выполняет кеширование на основе имени файла (поэтому два экземпляра + imageNamed: с тем же именем должны приводить к ссылкам на одни и те же кешированные данные), и кеш будет динамически расти, когда вы запрашиваете больше изображений через + imageNamed :. В iPhone OS 2.xa ошибка предотвращает сжатие кеша при получении предупреждения о памяти.

и

Насколько я понимаю, кеш + imageNamed: должен учитывать предупреждения о памяти на iPhone OS 3.0. Проверьте это, когда у вас будет возможность, и сообщите об ошибках, если вы обнаружите, что это не так.

Итак, вот оно. imageNamed: не будет разбивать ваши окна или убивать ваших детей. Это довольно просто, но это инструмент оптимизации. К сожалению, он плохо назван, и нет эквивалента, который был бы настолько прост в использовании - поэтому люди злоупотребляют им и расстраиваются, когда он просто выполняет свою работу.

Я добавил категорию в UIImage, чтобы исправить это:

// header omitted
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style.
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; {
    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]];
}

Ринсвинд также включил пример кода для создания вашей собственной оптимизированной версии. Я не вижу, чтобы оно того стоило, но вот оно для полноты картины.

CGImageRef originalImage = uiImage.CGImage;
CFDataRef imageData = CGDataProviderCopyData(
     CGImageGetDataProvider(originalImage));
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
CFRelease(imageData);
CGImageRef image = CGImageCreate(
     CGImageGetWidth(originalImage),
     CGImageGetHeight(originalImage),
     CGImageGetBitsPerComponent(originalImage),
     CGImageGetBitsPerPixel(originalImage),
     CGImageGetBytesPerRow(originalImage),
     CGImageGetColorSpace(originalImage),
     CGImageGetBitmapInfo(originalImage),
     imageDataProvider,
     CGImageGetDecode(originalImage),
     CGImageGetShouldInterpolate(originalImage),
     CGImageGetRenderingIntent(originalImage));
CGDataProviderRelease(imageDataProvider);
UIImage *decompressedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);

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

Рог
источник
Похоже, что в iOS11 [UIImage imageNamed:] не удаляет изображения из кеша, если изображения поступают из каталога xcasset. Это приводит к сбоям из-за нехватки памяти.
Juraj Antas
5

По моему опыту, кеш изображений, созданный imageNamed, не реагирует на предупреждения памяти. У меня было два приложения, которые были настолько скудными, насколько я мог их довести до управления памятью, но все равно по необъяснимым причинам вылетали из-за нехватки памяти. Когда я перестал использовать imageNamed для загрузки изображений, оба приложения стали значительно стабильнее.

Я признаю, что оба приложения загружали довольно большие изображения, но ничего необычного. В первом приложении я просто полностью пропустил кеширование, потому что маловероятно, что пользователь вернется к одному и тому же изображению дважды. Во втором случае я создал действительно простой класс кэширования, который делает именно то, что вы упомянули, - сохраняет UIImages в NSMutableDictionary и затем сбрасывает его содержимое, если я получил предупреждение о памяти. Если бы imageNamed: кешировали таким образом, я бы не увидел никакого повышения производительности. Все это работало на 2.2 - я не знаю, есть ли какие-то последствия для 3.0.

Вы можете найти мой другой вопрос по этой проблеме из моего первого приложения здесь: Вопрос StackOverflow о кешировании UIImage

Еще одно замечание - InterfaceBuilder использует imageNamed под покровом. О чем следует помнить, если вы столкнетесь с этой проблемой.

Bdebeez
источник
Я видел точно такое же поведение, и чтение из файла напрямую разрешило его. Кроме того, это происходит, когда я пытаюсь загрузить очень большое изображение - 11 456 x 3 226 пикселей, 15 МБ. Моя теория состоит в том, что системе не хватает памяти на полпути кэширования ImageNamed. И в этом случае нет встроенной обработки.
Dogweather