2010-01-24 11 views
5

Sto cercando di creare una classe che gestirà più download contemporaneamente (ho bisogno di scaricare molti piccoli file) e ho problemi con le connessioni "in via di estinzione".download di sfondo simultanei su iphone

Ho funzione addDonwload che aggiunge url all'elenco di URL da scaricare e controlla se è disponibile lo slot di download gratuito. Se ce n'è uno, avvia immediatamente il download. Quando termina uno dei download, prendo la prima lista di moduli di url e avvio il nuovo download.

io uso NSURLConnection per il download, qui è un codice

- (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 è la mia serie di slot di download (NSURLConnections) downloadQueue è elenco di URL per scaricare

ciò che accade è, all'inizio tutto funziona bene, ma dopo alcuni download le mie connessioni iniziano a scomparire. Il download inizia ma la connessione: didReceiveResponse: non viene mai chiamato. C'è una cosa nella console di output che non capisco che potrebbe aiutare un po '. Normalmente c'è qualcosa come 2010-01-24 21: 44: 17.504 appName [3057: 207] prima dei miei messaggi NSLog. Immagino che il numero tra parentesi quadre sia una specie di app: ID thread? tutto funziona ok mentre c'è lo stesso numero, ma dopo un po 'di tempo, "NSLog (@" download% @ all'indice% i ", info.url, indice);" i messaggi iniziano ad avere un secondo numero diverso. E quando ciò accade, smetto di ricevere qualsiasi callback per quella connessione url.

Questo mi sta facendo impazzire perché ho delle scadenze rigide e non riesco a trovare un problema. Non ho molte esperienze con iphone dev e applicazioni multithread. Ho provato approcci diversi, quindi il mio codice è piuttosto disordinato, ma spero che vedrete cosa sto cercando di fare qui :)

btw qualcuno di voi sa della classe esistente/lib che potrei usare sarebbe utile anche. Voglio download paralleli con capacità o aggiungere dinamicamente nuovi file da scaricare (quindi inizializzare il downloader con tutti gli URL non è utile per me)

risposta

2

Hai un po 'di problemi di memoria gravi, e problemi di sincronizzazione dei thread in questo codice.

Piuttosto che andare in tutti loro, chiederò alla seguente domanda: Stai facendo questo su uno sfondo filo di qualche tipo? Perché? IIRC NSURLConnection già fa IT di download su un thread in background e chiama il delegato sul thread che il NSURLConnection è stato creato su (ad esempio, il tuo thread principale in teoria).

Suggerisci fate un passo indietro, ri-leggere la documentazione NSURLConnection e quindi rimuovere il codice filettatura background e tutta la complessità che avete iniettato in questo inutilmente.

Ulteriore suggerimento: Invece di provare a mantenere il posizionamento parallelo in due array (e qualche codice impreciso nel precedente relativo a quello), creare un array e avere un oggetto che contiene sia NSURLConnection AND l'oggetto che rappresenta il risultato. Quindi è possibile rilasciare l'istanza di connessione var quando viene effettuata la connessione. E l'oggetto genitore (e quindi i dati) quando hai finito con i dati.

+0

grazie per i tuoi suggerimenti. Ho completamente riscritto la mia lezione e funziona come previsto. Ci scusiamo per il ritardo ma ho completamente dimenticato che questo è stato risolto molto tempo fa :) – Lope

0

Questo snippet può essere la fonte del bug, si rilascia l'oggetto a cui punta il puntatore activeInfo[index].delegate subito dopo l'emissione della chiamata al metodo asincrono su quell'oggetto.

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

questo downloader era in origine sequenziale e si stava lavorando grande, questa parte del codice viene da parte in precedenza funzionale. Ho pensato che performSelector conserva l'oggetto che invio come parametro. Comunque, anche se commento, non cambia nulla – Lope

0

Utilizzi connection:didFailWithError:? Potrebbe esserci un timeout che impedisce il corretto completamento del download.

Provare a sbarazzarsi dei blocchi @synchronized e vedere cosa succede.

La stringa all'interno delle parentesi quadre sembra essere identificatore di filo come avete indovinato. Quindi forse tieni bloccato nel @synchronized. A dire il vero, non vedo una ragione per il passaggio del filo - tutto il codice problematico deve essere eseguito nel thread principale (performSelectorOnMainThread) ...

In ogni caso, non v'è alcuna necessità di utilizzare sia la @synchronized e performSelectorOnMainThread.

BTW, non ho visto la linea NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];. Dove inizi la connessione?

Per quanto riguarda i download paralleli, penso che sia possibile scaricare più di un file alla volta con lo stesso codice che si utilizza qui. Basta creare una connessione separata per ogni download.

+0

La connessione è avviata nella funzione TryDownload ([_SyncConnection replaceObjectAtIndex: index withObject: [[NSURLConnection alloc] initWithRequest: richiesta delegato: self startImmediately: TRUE]] ) Io uso didFailWithError, ma è simile al download di successo e non viene chiamato quando perdo una delle connessioni, quindi l'ho rimosso (avrei dovuto dirlo, mi dispiace). Quando rimuovo il blocco @synchronized, ho ricevuto l'errore "changed while enumerating" anche se uso performSelectorOnMainThread. Stavo provando entrambi i metodi, ma nessuno dei due ha funzionato – Lope

+0

Prova ad usare NSNotificationCenter per avviare il nuovo download invece di performSelectorOnMainThread ... –

0

Considerate solo di mantenere una coda di download insieme al conteggio delle connessioni attive, scoppiando gli articoli fuori dalla coda della coda quando i download sono completi e uno slot diventa libero. È quindi possibile attivare gli oggetti NSURLConnection in modo asincrono ed elaborare gli eventi sul thread principale.

Se trovate che il vostro approccio parallelo vieta di fare tutta l'elaborazione sul thread principale, si consideri che gli oggetti gestore intermediario tra il codice di filo download principale e NSURLConnection. Usando questo approccio, istanzeresti il ​​tuo manager e fagli usare NSURLConnection in modo sincrono su un thread in background. Quel manager quindi si occupa completamente del download e passa il risultato al delegato del thread principale utilizzando un performSelectorOnMainThread: withObject: call. Ogni download è quindi solo un caso di creazione di un nuovo oggetto gestore quando hai uno slot libero e impostandolo.

1

vi consiglio di dare un'occhiata a questo: http://allseeing-i.com/ASIHTTPRequest/

Si tratta di un insieme piuttosto sofisticato di classi con termini di licenza liberali (gratuito troppo).

Si può fornire un sacco di funzionalità che si vogliono.

Problemi correlati