ios5 - AFNetworking+NsOperationQueue-Descargue miles de imágenes
(1)
Estoy trabajando en una tarea (iOS5 + solamente) que implica la descarga de miles de imágenes desde el servidor. Las imágenes pertenecen a ciertas categorías y cada categoría puede tener cientos de imágenes. Lo que necesito hacer es esto:
1) Asegúrese de que la aplicación descargue las imágenes que faltan en el fondo si la aplicación está activa (incluso cuando el usuario está navegando en otras áreas de la aplicación que no están relacionadas con las fotos).
2) Cuando el usuario hace clic en una categoría de foto, las imágenes en esa categoría se deben descargar como alta prioridad porque esas son las que deben ser visibles inmediatamente.
Todo lo anterior ocurre solo si la imagen no está disponible sin conexión. Una vez que se haya descargado, la imagen se usará desde el almacenamiento local.
Para resolver esto, la lógica que estoy usando es la siguiente:
1) En AppDelegate.m, en applicationDidBecomeActive
, empiezo a descargar las imágenes que faltan. Para hacer esto, realizo una consulta de Datos centrales, descubro qué imágenes faltan y comienzo a descargarlas en un subproceso con prioridad de FONDO. Algo como esto :-
dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(imageDownloadQueue, ^{
[DataDownloader downloadMissingImages];
});
dispatch_release(imageDownloadQueue);
El código de downloadMissingImages
ve así:
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount = 20;
for(MyImage *img in matches)
{
NSURLRequest *request = [NSURLRequest requestWithURL:img.photoUrl];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {
[MyImage imageFromAPI:image inManagedObjectContext:document.managedObjectContext];
NSLog(@"Successfully downloaded image for %@", img.title);
}];
[downloadQueue addOperation:operation];
}
Esto funciona, pero bloquea la interfaz de usuario principal y la aplicación se bloquea después de un tiempo. Esto es cuando intento descargar aproximadamente 700 imágenes. Con más imágenes, ciertamente se bloqueará.
2) Cuando un usuario hace clic en una categoría, necesito descargar esas imágenes primero, ya que deben mostrarse al usuario de inmediato. No estoy seguro de cómo puedo interrumpir la llamada a las imágenes que faltan y decirle que comience a descargar ciertas imágenes antes que los demás.
Entonces, básicamente, necesito descargar todas las imágenes que faltan en el fondo, pero si el usuario está navegando categoría de fotografía, esas imágenes deben tener alta prioridad en la cola de descarga.
No sé cómo hacer que esto funcione de manera eficiente. ¿Alguna idea?
Los registros de bloqueo se ven así
PAPP(36373,0xb065f000) malloc: *** mmap(size=16777216) failed (error code=12)
*** error: can''t allocate region
*** set a breakpoint in malloc_error_break to debug
PAPP(36373,0xb065f000) malloc: *** mmap(size=16777216) failed (error code=12)
*** error: can''t allocate region
*** set a breakpoint in malloc_error_break to debug
Jun 24 11:39:45 MacBook-Pro.local PAPP[36373] <Error>: ImageIO: JPEG Insufficient memory (case 4)
Gracias por adelantado.
Sobre el bloqueo, supongo que su aplicación se destruye debido a una de dos opciones:
la aplicación deja de responder (y por lo tanto no responde al proceso centinela de iOS);
demasiada memoria utilizada en el ciclo creando más de 700 operaciones de solicitud.
Para aclarar lo que realmente está sucediendo, debe proporcionar más información sobre el bloqueo (el registro de la consola). En cualquier caso, la solución sería cargar las imágenes en trozos de quizás 10 o 20 cada uno (podría ir incluso 1 por 1, si lo desea, no veo mucho problema con eso).
Sobre el segundo punto, ¿qué tal esto?
descargar una imagen de mayor prioridad en el hilo principal (a través de una descarga asíncrona, por supuesto, para evitar el bloqueo de la interfaz de usuario);
antes de iniciar la descarga de una imagen "fuera de línea", compruebe si la imagen ya se ha descargado mientras tanto a través de una descarga de "prioridad más alta".
Para manejar bien el punto 2, probablemente necesite poner en cola su operación personalizada en lugar de una AFImageRequestOperation
para hacer la verificación antes de la descarga real.
EDITAR:
Acerca de la descarga de imágenes en partes, lo que podría hacer es usar grupos de despacho para agrupar sus operaciones de red:
dispatch_group_t group = dispatch_group_create();
<your_core_data_query>
for (...) {
dispatch_group_enter(group);
NSURLRequest *request = [NSURLRequest requestWithURL:img.photoUrl];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {
[MyImage imageFromAPI:image inManagedObjectContext:document.managedObjectContext];
NSLog(@"Successfully downloaded image for %@", img.title);
dispatch_group_leave(group); //<== NOTICE THIS
}];
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
En esta muestra, estoy usando un grupo de envío para agrupar varias operaciones asíncronas y esperar a que se ejecuten todas; cuando devuelve dispatch_group_wait
, puede ejecutar otra ronda de eso (consultar datos centrales y luego enviar operaciones).
Acerca de su otra pregunta (¿cómo puedo verificar si una cola de mayor prioridad ya descargó una imagen determinada?), Debe hacer una consulta de datos centrales antes de ejecutar cada AFImageRequestOperation
; Una posibilidad es derivar su propia clase y anular el método de start
para hacer el control.
En ambas cuentas, podría simplificar mucho las lógicas de todo esto descargando las imágenes una a la vez (es decir, el bucle for (...)
no estaría allí, simplemente consulte la siguiente imagen para descargarla y descargarla; descargarlo comprueba que todavía no está allí.
Sugeriría ir por este camino más fácil.
Espero eso ayude.