2008-11-14 10 views
11

Tengo un UIScrollView grande en el que estoy colocando 3-4 celdas de imagen bastante grandes (320x1500 pixels o menos) UIImageView. Estoy agregando estos UIImageViews a la vista de desplazamiento dentro de mis archivos NIB. Tengo una salida en mi controlador, y eso es para UIScrollView. Estoy usando una propiedad (no atómica, retener) para esto y lo estoy sintetizando.Problema dealloc'ing memory utilizado por UIImageViews con una imagen bastante grande en un UIScrollView

Mi pregunta es esta: Cuando observo esto en el Monitor de la memoria, puedo ver que la memoria utilizada sube un poco cuando se carga la vista con todas estas imágenes (como se esperaba). Pero cuando dejo la vista, ella y su controlador son desasignados, pero no parecen ceder en ninguna parte cerca de la memoria que habían tomado. Cuando corté una de estas vistas (hay varias en mi aplicación) hasta solo 1-3 imágenes que eran 320x460 y dejé todo lo demás igual, recaptura la memoria muy bien.

¿Hay algún problema con el uso de imágenes de este tamaño? ¿Estoy haciendo algo mal en este código (pegado a continuación)?

Este es un fragmento del viewController que está causando problemas.

- (CGFloat)findHeight 
{ 
    UIImageView *imageView = nil; 
    NSArray *subviews = [self.scrollView subviews]; 

    CGFloat maxYLoc = 0; 
    for (imageView in subviews) 
    { 
      if ([imageView isKindOfClass:[UIImageView class]]) 
      { 
        CGRect frame = imageView.frame; 

        if ((frame.origin.y + frame.size.height) > maxYLoc) { 
          maxYLoc = frame.origin.y; 
          maxYLoc += frame.size.height; 
        } 
      } 
    } 
    return maxYLoc; 
} 

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    [self.scrollView setContentSize:CGSizeMake(320, [self findHeight])]; 

    [self.scrollView setCanCancelContentTouches:NO]; 
    self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite; 
    self.scrollView.clipsToBounds = YES;     
    self.scrollView.scrollEnabled = YES; 

    self.scrollView.pagingEnabled = NO; 
} 

- (void)dealloc { 
    NSLog(@"DAY Controller Dealloc'd"); 
    self.scrollView = nil; 
    [super dealloc]; 
} 

ACTUALIZACIÓN: He notado otro fenómeno extraño. Si no uso el desplazamiento en la vista, parece estar colgando en la memoria. Pero si me desplazo un montón y me aseguro de que todos los UIImageViews se vuelvan visibles en un punto, se liberarán y recuperarán la mayor parte de la memoria que perdió.

ACTUALIZACIÓN2: El motivo por el que le pregunto esto es porque mi aplicación se está bloqueando debido a la poca memoria. No me importaría si se tratara de almacenamiento en caché y el uso de memoria adicional, pero no parecen liberar cada vez que - incluso en condiciones didReceiveMmoryWarning

Respuesta

15

He resuelto el misterio, y estoy bastante seguro de que esto es un error del lado de Apple.

Como sugirió Kendall (¡gracias!), El problema radica en cómo InterfaceBuilder carga imágenes desde el archivo NIB. Cuando inicias FromFromNib, todos los UIImageViews se iniciarán con un UIImage usando el método imageNamed: de UIImage. Esta llamada usa almacenamiento en caché para la imagen. Normalmente, esto es lo que quieres. Sin embargo, con imágenes muy grandes y otras que se desplazan lejos del área visible, no parece estar obedeciendo las advertencias de memoria y volcando este caché. Esto es lo que creo que es un error del lado de Apple (por favor comente si está de acuerdo/en desacuerdo; me gustaría enviar esto si otros están de acuerdo). Como dije antes, la memoria utilizada por estas imágenes parece liberarse si un usuario se desplaza lo suficiente para que todo sea visible.

La solución provisional que he encontrado (también la sugerencia de Kendall) es dejar en blanco el nombre de la imagen en el archivo NIB. Así que diseñas tus elementos UIImageView de forma normal, pero no seleccionas una imagen. Luego, en su código viewDidLoad, ingresa y carga una imagen usando imageWithContentsOfFile: en su lugar. Este método NO almacena en caché la imagen y, por lo tanto, no causa ningún problema de memoria al conservar imágenes grandes.

Por supuesto, imageNamed: es mucho más fácil de usar, ya que se predetermina a cualquier cosa en el paquete, en lugar de tener que encontrar la ruta. Sin embargo, se puede obtener la ruta de acceso al paquete con el siguiente:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath]; 

Poner que en su conjunto, esto es lo que se ve como en el código:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]]; 
UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath]; 
imageView.image = loadImage; 

por lo que añadir que a mi código anterior, la la función completa se ve así:

- (CGFloat)findHeight 
{ 
    UIImageView *imageView = nil; 
    NSArray *subviews = [self.scrollView subviews]; 

    CGFloat maxYLoc = 0; 
    for (imageView in subviews) 
    { 
     if ([imageView isKindOfClass:[UIImageView class]]) 
     { 
      CGRect frame = imageView.frame; 

      if ((frame.origin.y + frame.size.height) > maxYLoc) { 
       maxYLoc = frame.origin.y; 
       maxYLoc += frame.size.height; 
      } 

      NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]]; 
      NSLog(fullpath); 


      UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath]; 
      imageView.image = loadImage; 
     } 
    } 
    return maxYLoc; 
} 
+0

un poco tarde pero ¿enviaste el error a Apple? Porque estoy de acuerdo con esto, es un error en ese lado. Nunca se borra. El mismo problema me está sucediendo al usar imageWithData. –

0
- (void)dealloc { 
    NSLog(@"DAY Controller Dealloc'd"); 
    [self.scrollView release]; 
    [super dealloc]; 
} 

dan que un tiro, su @property() definición es solicitando que se conserven, pero no estaba liberando explícitamente el objeto

+0

De hecho, he intentado esto. Hacerlo de la manera en que lo hice garantiza que el puntero se vuelva nulo después del lanzamiento. Esto se debe a que llama al colocador generado, que primero conserva nulo (sin efecto), luego libera scrollView y luego establece scrollView en nil. (También lo ejecuté hace un momento para verificar dos veces :)) – Bdebeez

4

Podría ser el sistema de caché las referencias a sus imágenes en la memoria, suponiendo que acaba de drogas en las imágenes desde el navegador de medios en IB el código debajo es probablemente usando el UIImageimageNamed: método ...

Puede intentar cargar todos los imágenes con imageWithContentsOfFile:, o imageWithData: y vea si se comporta igual (arrastrando en el UIImageViews no lleno en IB y configurando los contenidos en viewDidLoad :).

Lea la referencia de clase UIImage si desea un poco más de detalle, también describe qué métodos están en caché.

Si se trata de la memoria caché es probablemente correcto, ya que el sistema podría liberar si es necesario (¿También intenta golpear la memoria Simular advertencia en el simulador?)

0

Por lo que he aprendido en su comportamiento de la gestión de memoria es que las vistas no serán tratadas a menos que tengan poca memoria. Pruebe una demostración oficial como SQLBooks: 1. Ejecute con Leaks Monitor 2. Ejecute todas las vistas que tenga. 3. Regrese a la vista raíz. 4. Notará que el nivel de uso de la memoria sigue siendo el mismo. Como Kendall dijo que puede ser almacenado en caché?

Creo que no debe prestar atención a este patrón de uso de memoria, porque cuando las nuevas imágenes apuntan a UIScrollView, los objetos de imagen antiguos se liberarán y la memoria se liberará para las nuevas imágenes de todos modos.

0

Existe un problema conocido: una pérdida de memoria en imageName. Encontré una solución realmente útil: crear efectivo de imagen en el delegado de la aplicación, optimizando así el rendimiento y el uso de la memoria en mi aplicación. See this blog post

+0

Gracias por esta información. Esta página es un tesoro de frustrasionfixers. – Sneakyness

+0

El enlace de publicación de blog es 404 y no tuvo suerte al usar la búsqueda del sitio. – August

+1

Esta respuesta no está actualizada. El error se solucionó hace un par de años ... –

4

A parte de sus problemas de almacenamiento en caché imageNamed, la respuesta a su pregunta

tengo una gran UIScrollView en la que Estoy colocando 3-4 bastante grandes (320x1500 píxeles o así) Imagen UIImageView azulejos. [...] ¿Hay algún problema con el uso de imágenes de de este tamaño?

está en el encabezado de los documentos UIImage:

You should avoid creating UIImage objects that are greater than 1024 x 1024 in size. 
Besides the large amount of memory such an image would consume, you may run into 
problems when using the image as a texture in OpenGL ES or when drawing the image 
to a view or layer. 

Además, Apple afirma haber solucionado el problema imageNamed caché-lavado en 3.0 y más allá, a pesar de que no he probado esto extensivamente, yo .

+0

Actualmente me estoy encontrando con algunos problemas de memoria probablemente causados ​​por el almacenamiento en caché de imágenes de Imagined cuando realmente no los necesito en caché, ni quiero que estén en caché. Es como decirle a alguien que vaya a hacer x a 100 y, y en lugar de dejar cada y donde está cuando lo terminan, se los llevan a todos con ellos sin importar nada hasta que el peso de todos los aplasta y son delicado. – Sneakyness

+0

Así que no, no está corregido desde 3.1.2. – Sneakyness

+0

Ese comentario sobre la restricción del tamaño de la imagen parece un tanto extraño ya que el iPad se maneja de forma excelente con imágenes de ese tamaño y más grandes, ¿no? Entonces, ¿qué crees que están usando? – Josh

1

Completando la respuesta de Bdebeez.

Una buena idea es anular el imageNamed: llamando al imageWithContentsOfFile:.

Aquí es la idea del código:

@implementation UIImage(imageNamed_Hack) 

+ (UIImage *)imageNamed:(NSString *)name { 

    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name ] ]; 
} 

@end 

Nota:Con esta anulación no tendrá ningún UIImages caché de carga, si necesita esto, usted tendrá que poner en práctica su propia caché.

+2

Parece una idea extraordinariamente mala. –

+0

Anular los nombres predeterminados de métodos con categorías puede (y probablemente lo hará) generar un comportamiento indefinido. Si realmente desea anular este método, debe usar el método Swizzling. Sin embargo, el mejor enfoque es simplemente escribir su propio nombre de método imageNamedNoCache y esto si es necesario. –

Cuestiones relacionadas