2010-11-30 23 views
5

Estoy intentando escribir una aplicación para iPad que carga una imagen desde una URL. Estoy utilizando el siguiente código de la imagen de carga:Retraso significativo al cargar imágenes usando UIImage desde URL asincrónicamente

url = [NSURL URLWithString:theURLString]; 
    NSData *data = [NSData dataWithContentsOfURL:url]; 
    img = [[UIImage alloc] initWithData:data]; 
    [imageView setImage:img]; 
    [img release]; 
    NSLog(@"Image reloaded"); 

Todo ese código se agrega a una NSOperationQueue como una operación por lo que se carga de forma asíncrona y no causar mi aplicación para encerrar si websever de la imagen es lento. Agregué la línea NSLog para poder ver en la consola cuando este código terminó de ejecutarse.

He notado consistentemente que la imagen se actualiza en mi aplicación unos 5 segundos DESPUÉS de que el código termine de ejecutarse. Sin embargo, si utilizo este código por sí mismo sin ponerlo en NSOperationQUeue, parece que actualizo la imagen casi de inmediato.

El retraso no es causado completamente por un servidor web lento ... Puedo cargar la URL de la imagen en Safari y tarda menos de un segundo en cargar, o puedo cargarlo con el mismo código sin NSOperationQueue y carga mucho más rápido.

¿Hay alguna manera de reducir el retraso antes de que se muestre mi imagen pero seguir usando un NSOperationQueue?

Respuesta

6

De acuerdo con la documentación, el código que ha escrito no es válido. Es posible que los objetos UIKit no se llamen a ningún lado, sino a través del hilo principal. Apuesto a que lo que estás haciendo funciona en la mayoría de los aspectos, pero no altera la pantalla con éxito, y la pantalla se actualiza por coincidencia por algún otro motivo.

Apple recomienda encarecidamente que los subprocesos no sean la forma de realizar recuperaciones de URL asíncronas si desea que la batería sea eficiente. En su lugar, debe utilizar NSURLConnection y permitir que el runloop organice el comportamiento asincrónico. No es tan difícil escribir un método rápido que simplemente acumule datos en un NSData, ya que luego se lo envía a un delegado cuando la conexión se completa, pero suponiendo que prefiera seguir con lo que tiene, lo recomendaría. :

url = [NSURL URLWithString:theURLString]; 
NSData *data = [NSData dataWithContentsOfURL:url]; 
[self performSelectorOnMainThread:@selector(setImageViewImage:) withObject:data waitUntilDone:YES]; 

... 

- (void)setImageViewImage:(NSData *)data 
{ 
    img = [[UIImage alloc] initWithData:data]; 
    [imageView setImage:img]; 
    [img release]; 
    NSLog(@"Image reloaded"); 
} 

performSelectorOnMainThread hace lo que el nombre lo dice - el objeto se envía al programará el selector solicitada con el objeto dado como un solo parámetro en el hilo principal tan pronto como el bucle de ejecución puede llegar a ella. En este caso, 'data' es un objeto liberado automáticamente en el grupo en el hilo creado implícitamente por NSOperation. Como necesita que siga siendo válida hasta que se haya utilizado, he usado waitUntilDone:YES. Una alternativa sería hacer que los datos sean algo que usted posea explícitamente y que el método principal lo libere.

La desventaja principal de este método es que si la imagen vuelve comprimida (como JPEG o PNG), se descomprimirá en el hilo principal. Para evitar eso sin hacer suposiciones empíricas sobre el comportamiento de UIImage que va más allá de lo que está documentado como seguro, necesitarás bajar al nivel C y usar CoreGraphics. Pero lo tomo dado que hacerlo está más allá del alcance de esta pregunta.

+0

Gracias, Tommy! Lo echaré un vistazo esta noche y veré a dónde puedo llegar con lo que me has contado.En realidad, esta mañana estaba leyendo diferentes formas de descargar imágenes en Coacoa, y descubrí por mi cuenta que cuando reescribí el código de manejo de imágenes completo usando NSUrlRequest y NSURLConnection, parecía cargar la imagen como esperaba. Todavía no estoy seguro de si usaré el nuevo método que escribí o el código que me acaba de mostrar, pero tener opciones es excelente, y escribirlo a ambos lados fue una buena experiencia de aprendizaje. ¡Gracias de nuevo por su ayuda y gracias por aguantar a un novato como yo! :) – Jackson

+0

Por cierto, si alguien más lee esta pregunta, encontré este artículo muy útil para implementar un cargador de imágenes basado en NSUrlRequest. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html – Jackson

1

Tommy tiene razón sobre la necesidad de hacer todas las cosas de UIKit en el hilo principal. Sin embargo, si está ejecutando la búsqueda en una cola de operaciones en segundo plano, no es necesario utilizar la carga asíncrona NSURLConnection. Además, al mantener la descodificación de imagen en funcionamiento en segundo plano, evitará que el hilo principal se bloquee mientras se decodifica la imagen.

Usted debe ser capaz de utilizar su código original como es, pero sólo cambiar [imgView setImage: img] a:

[imageView performSelectorOnMainThread:@selector(setImage:) 
          withObject:img 
         waitUntilDone:NO]; 
+0

Vaya, eso solucionó el problema. ¡Muchas gracias! ¡Eso fue tan simple! Todavía no estoy del todo seguro de por qué. Tendré que leer sobre esto. Ahora que tengo un método NSUrlConnection completamente implementado y en funcionamiento (que por sí solo es asíncrono) y un método NSData/dataWithContentsOfURL usando NSObjectQueue para el método asincrónico que funciona, ¿cuál recomienda que use? ¿Cuáles son los puntos fuertes de usar NSUrlConnection vs. NSData y viceversa? He oído que NSUrlConnection tiene más flexibilidad con respecto al almacenamiento en caché y maneja mejor los errores. ¿Es eso cierto? ¡Gracias de nuevo! – Jackson

+0

Es cierto que NSURLConnection le brinda más flexibilidad. Pero al usar la carga asíncrona de NSURLConnection en un fondo, NSOperation es bastante complicado, porque tendrás que configurar tu propio ciclo de ejecución. Puede usar el método '- [NSURLConnection sendSynchronousRequest: returningResponse: error:]' para obtener algo de la flexibilidad ofrecida por NSURLConnection (consulte NSMutableURLRequest) mientras mantiene las cosas sincrónicas. Probablemente sea eso lo que haría, a menos que tenga requisitos complejos de redirección/autenticación/caché HTTP. –

+0

En cuanto al "por qué", esta solución funciona es que el código de dibujo de vista que se ejecuta en el hilo principal necesita saber que acaba de cambiar la imagen de la vista de la imagen. Al llamar a setImage: en un hilo de fondo, presumiblemente nunca se notifica al hilo principal que hay contenido nuevo que se dibujará (hasta que algo más haga que la vista se actualice). Es posible que pueda obtener un comportamiento mucho peor haciendo esto en el hilo de fondo, p. si cambia la imagen mientras el hilo principal está en el medio de intentar dibujarla. –

Cuestiones relacionadas