Расселение изображения UIImageNamed: FUD

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

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

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

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

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

Ответ 1

TL;DR: 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 ошибка предотвращает сжатие кеша при получении предупреждения о памяти.

и

Я понимаю, что кеш + 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 также включил некоторый пример кода для создания собственной оптимизированной версии. Я не вижу, что это стоит maintentace, но здесь для полноты.

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);

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

Ответ 2

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

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

Вы можете найти мой другой вопрос по этому вопросу из моего первого приложения здесь: fooobar.com/info/50333/...

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