Разогнать UIImage imageNamed: FUD
Редактировать февраль 2014: обратите внимание, что этот вопрос относится к iOS 2.0! Требования к изображениям и их обработка с тех пор сильно изменились. Retina делает изображения больше и загружает их немного сложнее. Благодаря встроенной поддержке изображений iPad и сетчатки, вы обязательно должны использовать ImageNamed в своем коде.
Я вижу много людей, говорящих imageNamed
это плохо, но равное количество людей говорят, что производительность хорошая - особенно при рендеринге UITableView
s. Посмотрите этот вопрос, например, или эту статью на iPhoneDeveloperTips.com
UIImage
"s imageNamed
метод, используемый для утечки, поэтому его лучше избегать, но он был исправлен в последних выпусках. Я хотел бы лучше понять алгоритм кэширования, чтобы принять обоснованное решение о том, где я могу доверять системе для кэширования моих изображений и где мне нужно пройти лишнюю милю и сделать это самому. Мое текущее базовое понимание заключается в том, что это простой NSMutableDictionary
из UIImages
ссылается по имени файла. Он становится больше, а когда заканчивается память, он становится намного меньше.
Например, кто-нибудь знает наверняка, что кеш изображений позади imageNamed
не отвечает на didReceiveMemoryWarning
? Кажется маловероятным, что Apple не сделает этого.
Если у вас есть понимание алгоритма кэширования, пожалуйста, опубликуйте его здесь.
2 ответа
tldr: ImagedNamed - это хорошо. Хорошо справляется с памятью. Используйте это и перестаньте беспокоиться.
Изменить ноябрь 2012: обратите внимание, что этот вопрос относится к iOS 2.0! Требования к изображениям и их обработка с тех пор сильно изменились. Retina делает изображения больше и загружает их немного сложнее. Благодаря встроенной поддержке изображений iPad и сетчатки, вы обязательно должны использовать ImageNamed в своем коде. Теперь ради потомков:
Сестринская ветка на форумах Apple Dev получила немного лучшего трафика. В частности, Rincewind добавил некоторые полномочия.
В iPhone OS 2.x есть проблемы, когда кэш imageNamed: не будет очищен, даже после предупреждения памяти. В то же время +imageNamed: получил широкое применение не для кэша, а для удобства, что, вероятно, усугубило проблему больше, чем следовало бы.
предупреждая, что
Что касается скорости, существует общее недопонимание того, что происходит. Самая большая вещь, которую +imageNamed: делает - это декодирует данные изображения из исходного файла, что почти всегда значительно увеличивает размер данных (например, PNG-файл размером с экран может сжать несколько десятков КБ при сжатии, но потребляет более половины МБ). распакованный - ширина * высота * 4). Напротив +imageWithContentsOfFile: распаковывает это изображение каждый раз, когда нужны данные изображения. Как вы можете себе представить, если вам нужны только данные изображения один раз, вы ничего не выиграете, за исключением того, что у вас будет кешированная версия изображения, вероятно, дольше, чем вам нужно. Однако, если у вас есть большое изображение, которое нужно часто перерисовывать, есть альтернативы, хотя я бы рекомендовал в первую очередь избегать перерисовки этого большого изображения:).
Что касается общего поведения кеша, он выполняет кеширование на основе имени файла (поэтому два экземпляра +imageNamed: с одним и тем же именем должны приводить к ссылкам на одни и те же кешированные данные), и кеш будет расти динамически, когда вы запрашиваете больше изображений через +imageNamed:. В iPhone OS 2.x a ошибка препятствует сжатию кэша при получении предупреждения о памяти.
а также
Насколько я понимаю, кэш +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]];
}
Rincewind также включил пример кода для создания своей собственной оптимизированной версии. Я не вижу, что это стоит того, но здесь это для полноты.
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);
Компромисс с этим кодом заключается в том, что декодированное изображение использует больше памяти, но рендеринг происходит быстрее.
По моему опыту, кэш изображений, созданный imageNamed, не отвечает на предупреждения памяти. У меня было два приложения, которые были настолько скудны, насколько я мог получить их до управления мемами, но все еще необъяснимо зависали из-за отсутствия мемов. Когда я перестал использовать imageNamed для загрузки изображений, оба приложения стали значительно более стабильными.
Я признаю, что оба приложения загружали несколько больших изображений, но ничего такого, что было бы совершенно необычным. В первом приложении я просто полностью пропустил кэширование, так как маловероятно, что пользователь дважды вернется к одному и тому же изображению. Во втором я построил действительно простой класс кэширования, выполняя то, что вы упомянули - сохраняя UIImages в NSMutableDictionary, а затем сбрасывая его содержимое, если я получил предупреждение о памяти. Если бы imageNamed: так кешировать, то я не должен был видеть никакого повышения производительности. Все это работало на 2.2 - я не знаю, есть ли какие-либо последствия для 3.0.
Мой другой вопрос по этой проблеме вы можете найти в моем первом приложении здесь: Stackru вопрос о кешировании UIImage
Еще одно замечание - InterfaceBuilder использует imageNamed под крышками. Что-то иметь в виду, если вы столкнетесь с этой проблемой.