Disipando la imagen de UIImage Nombre: FUD

117

Editar febrero de 2014: tenga en cuenta que esta pregunta data de iOS 2.0. Los requisitos y el manejo de las imágenes han cambiado mucho desde entonces. Retina hace que las imágenes sean más grandes y cargarlas un poco más complejo. Con el soporte integrado para imágenes de iPad y retina, ciertamente debería usar ImageNamed en su código .

Veo a mucha gente que dice que imageNamedes malo, pero un número igual de personas que dice que el rendimiento es bueno, especialmente al renderizar UITableViews. Vea esta pregunta SO, por ejemplo, o este artículo en iPhoneDeveloperTips.com

UIImageEl imageNamedmétodo utilizado para filtrar fugas, por lo que es mejor evitarlo, pero se ha corregido en versiones recientes. Me gustaría entender mejor el algoritmo de almacenamiento en caché para poder tomar una decisión razonada sobre dónde puedo confiar en el sistema para almacenar en caché mis imágenes y dónde debo hacer un esfuerzo adicional y hacerlo yo mismo. Mi entendimiento básico actual es que se trata de un sencillo NSMutableDictionaryde UIImagesreferencia por nombre de archivo. Se hace más grande y cuando la memoria se agota se vuelve mucho más pequeña.

Por ejemplo, ¿alguien sabe con certeza que el caché de imágenes detrás imageNamedno responde didReceiveMemoryWarning? Parece poco probable que Apple no hiciera esto.

Si tiene alguna idea sobre el algoritmo de almacenamiento en caché, publíquela aquí.

Rog
fuente
2
yo también. Parece que SO no está tan lleno de genios como pensaba.
Rog
Parece que has pasado algún tiempo investigando esto. ¿Ha realizado alguna experimentación que muestre algún impacto negativo de la imagen de UIImage, nombrada con la última versión cocoa-touch? Cree un UITableView y agregue muchas (muchos miles) filas y vea si el rendimiento disminuye cuando muestra una imagen diferente en cada fila. Quizás entonces la gente pueda comentar sus hallazgos.
stefanB
En realidad, no he hecho muchos experimentos. Utilizo imageNamed y, aunque todavía tengo que optimizar la memoria de esta aplicación, no tengo nada que pueda señalar directamente a imageNamed como un acaparador de memoria. He señalado algunas de las publicaciones del blog que se quejan de imagedNamed aquí y he hecho una pregunta similar en los foros de Apple que obtuvieron más acción. Volveré a publicar un resumen y un enlace aquí cuando tenga tiempo y sienta que ha tenido y las respuestas que va a recibir.
Rog

Respuestas:

85

tldr: ImagedNamed está bien. Maneja bien la memoria. Úsalo y deja de preocuparte.

Editar noviembre de 2012 : tenga en cuenta que esta pregunta data de iOS 2.0. Los requisitos y el manejo de las imágenes han cambiado mucho desde entonces. Retina hace que las imágenes sean más grandes y cargarlas un poco más complejo. Con el soporte integrado para imágenes de iPad y retina, ciertamente debería usar ImageNamed en su código. Ahora, por el bien de la posteridad:

El hilo hermano de los foros de desarrolladores de Apple recibió un mejor tráfico. Específicamente, Rincewind agregó algo de autoridad.

Hay problemas en iPhone OS 2.x en los que el caché imageNamed: no se borra, incluso después de una advertencia de memoria. Al mismo tiempo, + imageNamed: se ha utilizado mucho no por la caché, sino por la conveniencia, que probablemente ha magnificado el problema más de lo que debería haber sido.

mientras advierte que

En el frente de la velocidad, hay un malentendido general de lo que está sucediendo. Lo más importante que hace + imageNamed: es decodificar los datos de la imagen del archivo de origen, lo que casi siempre aumenta significativamente el tamaño de los datos (por ejemplo, un archivo PNG del tamaño de una pantalla puede consumir unas pocas docenas de KB cuando se comprime, pero consume más de medio MB descomprimido - ancho * alto * 4). Por el contrario, + imageWithContentsOfFile: descomprimirá esa imagen cada vez que se necesiten los datos de la imagen. Como puede imaginar, si solo necesita los datos de la imagen una vez, no ha ganado nada aquí, excepto para tener una versión en caché de la imagen colgando, y probablemente por más tiempo del que necesita. Sin embargo, si tiene una imagen grande que necesita volver a dibujar con frecuencia, existen alternativas, aunque la que recomendaría principalmente es evitar volver a dibujar esa imagen grande :).

Con respecto al comportamiento general de la caché, la caché se basa en el nombre de archivo (por lo que dos instancias de + imageNamed: con el mismo nombre deberían dar como resultado referencias a los mismos datos en caché) y la caché crecerá dinámicamente a medida que solicite más imágenes a través de + imageNamed :. En iPhone OS 2.xa, el error evita que la caché se reduzca cuando se recibe una advertencia de memoria.

y

Tengo entendido que + imageNamed: caché debe respetar las advertencias de memoria en iPhone OS 3.0. Pruébelo cuando tenga la oportunidad e informe los errores si descubre que este no es el caso.

Así que ahí lo tienes. imageNamed: no romperá sus ventanas ni asesinará a sus hijos. Es bastante simple pero es una herramienta de optimización. Lamentablemente, tiene un nombre incorrecto y no hay equivalente que sea tan fácil de usar; por lo tanto, la gente lo usa en exceso y se molesta cuando simplemente hace su trabajo.

Agregué una categoría a UIImage para solucionar eso:

// 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 también incluyó un código de ejemplo para crear su propia versión optimizada. No veo que valga la pena el mantenimiento, pero aquí está por completo.

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

La compensación con este código es que la imagen decodificada usa más memoria pero la renderización es más rápida.

Rog
fuente
Parece que en iOS11, [UIImage imageNamed:] no está desalojando imágenes de la caché si las imágenes provienen del catálogo de xcasset. Esto conduce a bloqueos debido a la falta de memoria.
Juraj Antas
5

En mi experiencia, el caché de imágenes creado por imageNamed no responde a las advertencias de memoria. He tenido dos aplicaciones que eran lo más sencillas que podía conseguir en cuanto a la gestión de mem, pero seguían fallando inexplicablemente debido a la falta de mem. Cuando dejé de usar imageNamed para cargar las imágenes, ambas aplicaciones se volvieron mucho más estables.

Debo admitir que ambas aplicaciones cargaron imágenes algo grandes, pero nada que fuera totalmente fuera de lo común. En la primera aplicación, simplemente omití el almacenamiento en caché porque era poco probable que un usuario regresara a la misma imagen dos veces. En el segundo, construí una clase de almacenamiento en caché realmente simple haciendo exactamente lo que mencionaste: mantener UIImages en un NSMutableDictionary y luego vaciar su contenido si recibía una advertencia de memoria. Si imageNamed: se almacenara en caché así, entonces no debería haber visto ninguna mejora de rendimiento. Todo esto se estaba ejecutando en 2.2, no sé si hay implicaciones 3.0 en esto.

Puede encontrar mi otra pregunta sobre este problema de mi primera aplicación aquí: Pregunta de StackOverflow sobre el almacenamiento en caché de UIImage

Otra nota: InterfaceBuilder usa imageNamed debajo de las cubiertas. Algo a tener en cuenta si se encuentra con este problema.

Bdebeez
fuente
Vi exactamente el mismo comportamiento y la lectura del archivo lo resolvió directamente. Además, sucede cuando intento cargar una imagen muy grande: 11,456 x 3,226 px, 15MB. Mi teoría es que el sistema se queda sin memoria en la mitad de la operación de caché ImageNamed. Y no hay manejo integrado para este caso.
Dogweather