2010-01-24 23 views
5

Estoy tratando de crear una clase que maneje múltiples descargas al mismo tiempo (necesito descargar una gran cantidad de archivos pequeños) y tengo problemas con las conexiones que "desaparecen".descargas simultáneas de fondo en el iphone

Tengo la función addDonwload que agrega la url a la lista de URL para descargar, y comprueba si hay una ranura de descarga gratuita disponible. Si hay uno, comienza la descarga inmediatamente. Cuando finaliza una de las descargas, elijo la primera lista de formularios de la url y comienzo una nueva descarga.

utilizo NSURLConnection para descargar, aquí hay un código de

- (bool) TryDownload:(downloadInfo*)info 
{ 
    int index; 
    @synchronized(_asyncConnection) 
    { 
     index = [_asyncConnection indexOfObject:nullObject]; 
     if(index != NSNotFound) 
     { 
      NSLog(@"downloading %@ at index %i", info.url, index); 
      activeInfo[index] = info; 
      NSURLRequest *request = [NSURLRequest requestWithURL:info.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15]; 

      [_asyncConnection replaceObjectAtIndex:index withObject:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:TRUE]]; 
      //[[_asyncConnection objectAtIndex:i] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];   

      return true; 
     } 
    } 

    return false; 
} 

- (void)connectionDidFinishLoading:(NSURLConnection*)connection 
{ 
    [self performSelectorOnMainThread:@selector(DownloadFinished:) withObject:connection waitUntilDone:false]; 
} 

- (void)DownloadFinished:(id)connection 
{ 
    NSInteger index = NSNotFound; 
    @synchronized(_asyncConnection) 
    { 
     index = [_asyncConnection indexOfObject:(NSURLConnection*)connection]; 
    } 

    [(id)activeInfo[index].delegate performSelectorInBackground:@selector(backgroundDownloadSucceededWithData:) withObject:_data[index]]; 
    [_data[index] release]; 
    [activeInfo[index].delegate release]; 
    @synchronized(_asyncConnection) 
    { 
     [[_asyncConnection objectAtIndex:index] release]; 
     [_asyncConnection replaceObjectAtIndex:index withObject:nullObject];    
    } 
    @synchronized(downloadQueue) 
    { 
     [downloadQueue removeObject:activeInfo[index]]; 
     [self NextDownload]; 
    } 
} 

- (void)NextDownload 
{ 
    NSLog(@"files remaining: %i", downloadQueue.count); 
    if(downloadQueue.count > 0) 
    { 
     if([self TryDownload:[downloadQueue objectAtIndex:0]]) 
     { 
      [downloadQueue removeObjectAtIndex:0]; 
     } 
    } 
} 

_asyncConnection es mi serie de ranuras de descarga (NSURLConnections) downloadQueue está la lista de direcciones URL para descargar

Lo que sucede es, al principio todo funciona bien, pero después de algunas descargas, mis conexiones comienzan a desaparecer. La descarga comienza pero la conexión: didReceiveResponse: nunca se llama. Hay una cosa en la consola de salida que no entiendo que podría ayudar un poco. Normalmente hay algo así como 2010-01-24 21: 44: 17.504 appName [3057: 207] antes de mis mensajes NSLog. Supongo que ese número entre corchetes es algún tipo de aplicación: ¿identificación de hilo? todo funciona bien mientras hay el mismo número, pero después de un tiempo, "NSLog (@" descargando% @ en el índice% i ", info.url, índice);" los mensajes comienzan teniendo diferente ese segundo número. Y cuando eso sucede, dejo de recibir cualquier devolución de llamada para esa conexión url.

Esto me ha estado volviendo loco ya que tengo plazos estrictos y no puedo encontrar el problema. No tengo muchas experiencias con el desarrollo de iphone y aplicaciones multiproceso. He estado probando diferentes enfoques, así que mi código es un poco desordenado, pero espero que veas lo que trato de hacer aquí :)

btw es cualquiera de ustedes que conozca la clase/lib existente que podría usar que sería útil también. Quiero descargas paralelas con la capacidad o dinámicamente añadir nuevos archivos para descargar (por lo que la inicialización de descarga al principio con todas las URL no es útil para mí)

Respuesta

2

Tiene un montón de problemas graves de memoria y problemas de sincronización de subprocesos en este código.

En lugar de entrar en todas, haré la siguiente pregunta: ¿Estás haciendo esto en un hilo de fondo de algún tipo? ¿Por qué? IIRC NSURLConnection ya lo hace en un hilo de fondo y llama a su delegado en el hilo en el que se creó el NSURLConnection (por ejemplo, su hilo principal idealmente).

Le sugerimos que retroceda, vuelva a leer la documentación de NSURLConnection y luego elimine el código de enrutamiento de fondo y toda la complejidad que ha inyectado en esto innecesariamente.

Sugerencia adicional: En lugar de tratar de mantener el posicionamiento paralelo en dos matrices (y algún código incompleto en lo anterior relacionado con eso), haga una matriz y tenga un objeto que contenga tanto NSURLConnection como el objeto que representa el resultado. Luego puede simplemente liberar la var de la instancia de conexión cuando la conexión está lista. Y el objeto principal (y por lo tanto los datos) cuando haya terminado con los datos.

+0

gracias por tus consejos. Reescribí completamente mi clase y funciona como esperaba. Lo siento por la demora, pero olvidé por completo que esto se resolvió hace mucho tiempo :) – Lope

0

Este fragmento puede ser la fuente del error, se suelta el objeto apuntado por el puntero activeInfo[index].delegate justo después de emitir una llamada al método asíncrono en ese objeto.

[(id)activeInfo[index].delegate performSelectorInBackground:@selector(backgroundDownloadSucceededWithData:) withObject:_data[index]]; 
[_data[index] release]; 
[activeInfo[index].delegate release]; 
+0

este programa de descarga fue originalmente secuencial y funcionaba bien, esta parte del código proviene de una parte previamente funcional. Pensé que performSelector conserva el objeto que envío como parámetro. De todos modos, incluso si lo comento, no cambia nada – Lope

0

¿Usas connection:didFailWithError:? Puede haber un tiempo de espera que impide la finalización exitosa de la descarga.

Trate de deshacerse de los bloques @synchronized y vea qué pasa.

La cadena dentro de los corchetes parece ser el identificador de subprocesos como usted lo ha adivinado. Entonces quizás te bloqueen en el @synchronized. En realidad, no veo una razón para cambiar de hilo - todo el código problemática se debe ejecutar en el hilo principal (performSelectorOnMainThread) ...

De todos modos, no hay necesidad de utilizar tanto el @synchronized y la performSelectorOnMainThread.

Por cierto, no vi la línea NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];. ¿Dónde inicias la conexión?

En cuanto a las descargas paralelas, creo que puede descargar más de un archivo a la vez con el mismo código que usa aquí. Solo crea una conexión separada para cada descarga.

+0

La conexión se inicia en la función TryDownload ([_asyncConnection replaceObjectAtIndex: index withObject: [[NSURLConnection alloc] initWithRequest: request delegate: self startImmediately: TRUE]]; ) Uso didFailWithError, pero es similar a descargar con éxito y no se llama cuando pierdo una de las conexiones, así que lo eliminé (debería haberlo mencionado, lo siento). Cuando elimino el bloque @synchronized recibí el error "changed while enumerating" incluso si uso performSelectorOnMainThread. Estaba probando ambos métodos, pero ninguno de ellos funcionó – Lope

+0

Intenta usar NSNotificationCenter para iniciar la nueva descarga en lugar de performSelectorOnMainThread ... –

0

Considere la posibilidad de mantener una cola de descargas junto con un recuento de las conexiones activas, colocando elementos fuera de la cola cuando se completan las descargas y se libera una ranura. A continuación, puede disparar NSURLConnection objetos asincrónicamente y procesar eventos en el hilo principal.

Si ve que su enfoque paralelo prohíbe hacer todo el procesamiento en el hilo principal, considere la posibilidad de tener objetos de administrador intermedio entre el código de descarga del hilo principal y NSURLConnection. Al usar ese enfoque, creará una instancia de su administrador y conseguirá que use NSURLConnection de forma síncrona en una cadena de fondo. Ese gerente luego se ocupa completamente de la descarga y pasa el resultado a su delegado de subproceso principal utilizando una llamada performSelectorOnMainThread: withObject:. Cada descarga es simplemente un caso de creación de un nuevo objeto de administrador cuando tiene un espacio libre y lo pone en marcha.

1

recomiendo que tome un vistazo a esto: http://allseeing-i.com/ASIHTTPRequest/

Es un conjunto muy sofisticado de las clases con los términos de licencia liberales (también gratis).

Puede proporcionar mucha de la funcionalidad que usted quiere.