2015-01-30 4 views
15

Sto usando cURL per scaricare circa 1700+ file - che ammontano a circa ~ 290 MB - nella mia app iOS. Ci vogliono circa 5-7 minuti sulla mia connessione Internet per scaricarli tutti usando cURL. Ma dal momento che non tutti hanno una connessione internet veloce (soprattutto quando si è in viaggio), ho deciso di consentire il download dei file in background, in modo che l'utente possa fare altre cose in attesa del completamento del download. È qui che viene in NSURLSession.Perché NSURLSession è più lento di cURL quando si scaricano molti file?

Utilizzando NSURLSession, ci vogliono circa 20+ minuti sulla mia connessione a Internet per scaricare tutti loro mentre l'applicazione è in primo piano. Non mi interessa che sia lento quando l'app è in background, perché capisco che spetta al sistema operativo pianificare i download. Ma è un problema quando è lento anche quando è in primo piano. È questo il comportamento previsto? È a causa della quantità dei file?

Nel caso in cui non sto usando NSURLSession correttamente, ecco un frammento di come lo sto usando:

// Initialization 

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"<my-identifier>"]; 
sessionConfiguration.HTTPMaximumConnectionsPerHost = 40; 

backgroundSession = [NSURLSession sessionWithConfiguration:sessionConfiguration 
                delegate:self 
              delegateQueue:nil]; 

// ... 

// Creating the tasks and starting the download 
for (int i = 0; i < 20 && queuedRequests.count > 0; i++) { 
    NSDictionary *requestInfo = [queuedRequests lastObject]; 
    NSURLSessionDownloadTask *downloadTask = [backgroundSession downloadTaskWithURL:[NSURL URLWithString:requestInfo[@"url"]]]; 
    ongoingRequests[@(downloadTask.taskIdentifier)] = requestInfo; 
    [downloadTask resume]; 
    [queuedRequests removeLastObject]; 
    NSLog(@"Begin download file %d/%d: %@", allRequests.count - queuedRequests.count, allRequests.count, requestInfo[@"url"]); 
} 

// ... 

// Somewhere in (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 

// After each download task is completed, grab a file to download from 
// queuedRequests, and create another task 

if (queuedRequests.count > 0) { 
    requestInfo = [queuedRequests lastObject]; 
    NSURLSessionDownloadTask *newDownloadTask = [backgroundSession downloadTaskWithURL:[NSURL URLWithString:requestInfo[@"url"]]]; 
    ongoingRequests[@(newDownloadTask.taskIdentifier)] = requestInfo; 

    [newDownloadTask resume]; 
    [queuedRequests removeLastObject]; 
    NSLog(@"Begin download file %d/%d: %@", allRequests.count - queuedRequests.count, allRequests.count, requestInfo[@"url"]); 
} 

Ho anche provato ad utilizzare molteplici NSURLSession, ma è ancora lento. La ragione per cui l'ho provato è che quando utilizzo cURL, creo più thread (circa 20) e ogni thread scaricherà un singolo file alla volta.

Inoltre, non è possibile ridurre il numero di file bloccandolo, perché ho bisogno che l'app sia in grado di scaricare singoli file poiché li aggiornerò di volta in volta. Fondamentalmente, quando l'app si avvia, controlla se ci sono dei file che sono stati aggiornati e scaricano solo quei file. Poiché i file sono archiviati in S3 e S3 non ha il servizio di zipping, non è possibile comprimerli in un singolo file al volo.

+1

Provare a sostituire 'backgroundSessionConfigurationWithIdentifier:' con 'defaultSessionConfiguration' e confrontare i risultati. Non penso che a iOS importi davvero se la tua app è in esecuzione o meno durante il download su una sessione configurata per download in background. Farà sempre quei download su un processo separato che accelererà la velocità. –

+1

1. Sono d'accordo con Filip: provate temporaneamente ad usare la sessione non in background e penso che potreste vedere la differenza di prestazioni. I miei test aneddotici suggerivano che la sessione in background (anche sparata dal foreground) era più lenta, anche se non l'ho ancora testata di recente. – Rob

+3

2.Quando si utilizza la sessione in background, non è necessario "accodare il primo 20 processo". Basta accodarli tutti. Misurandoli in questo modo, rallenterai il processo in background perché continuerà a riaccendere la tua app se sospesa/terminata. La sessione in primo piano richiede il controllo del grado di concorrenza per evitare timeout, ma non per le sessioni in background. (Poi di nuovo, se stavo controllando il grado di concorrenza in una sessione in primo piano, penso che la coda di operazioni sia la soluzione più semplice in quel caso. Ma forse è accademico se usi la sessione in background.) – Rob

risposta

22

Come accennato da Filip e Rob nei commenti, la lentezza è dovuta al fatto che quando NSURLSession viene inizializzato con backgroundSessionConfigurationWithIdentifier:, le attività di download verranno eseguite in background, indipendentemente se l'app è in primo piano. Così ho risolto questo problema da avere 2 casi di NSURLSession: uno per il primo piano download, e uno per lo sfondo scaricare:

NSURLSessionConfiguration *foregroundSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 
foregroundSessionConfig.HTTPMaximumConnectionsPerHost = 40; 

foregroundSession = [NSURLSession sessionWithConfiguration:foregroundSessionConfig 
                delegate:self 
              delegateQueue:nil]; 
[foregroundSession retain]; 

NSURLSessionConfiguration *backgroundSessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.terato.darknessfallen.BackgroundDownload"]; 
backgroundSessionConfig.HTTPMaximumConnectionsPerHost = 40; 

backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfig 
                delegate:self 
              delegateQueue:nil]; 
[backgroundSession retain]; 

Quando l'applicazione viene commutato a fondo, ho semplicemente chiamare cancelByProducingResumeData: su ciascuna delle attività di download che è ancora esecuzione, e quindi passarlo a downloadTaskWithResumeData::

- (void)switchToBackground 
{ 
    if (state == kDownloadManagerStateForeground) { 
     [foregroundSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
      for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { 
       [downloadTask cancelByProducingResumeData:^(NSData *resumeData) { 
        NSURLSessionDownloadTask *downloadTask = [backgroundSession downloadTaskWithResumeData:resumeData]; 
        [downloadTask resume]; 
       }]; 
      } 
     }]; 

     state = kDownloadManagerStateBackground; 
    } 
} 

Analogamente, quando l'applicazione viene commutato primo piano, faccio lo stesso ma commutato foregroundSession con backgroundSession:

- (void)switchToForeground 
{ 
    if (state == kDownloadManagerStateBackground) { 
     [backgroundSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
      for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { 
       [downloadTask cancelByProducingResumeData:^(NSData *resumeData) { 
        NSURLSessionDownloadTask *downloadTask = [foregroundSession downloadTaskWithResumeData:resumeData]; 
        [downloadTask resume]; 
       }]; 
      } 
     }]; 

     state = kDownloadManagerStateForeground; 
    } 
} 

Inoltre, non dimenticare di chiamare beginBackgroundTaskWithExpirationHandler: prima di chiamare switchToBackground quando l'app passa allo sfondo. Questo per garantire che il metodo possa essere completato mentre è in background. Altrimenti, verrà chiamato solo una volta che l'app sarà nuovamente in primo piano.

+0

Buon lavoro, non dimenticare di accettare la tua risposta per contrassegnare la domanda come risposta. –

+0

lo chiami due volte, 'foregroundSessionConfig.HTTPMaximumConnectionsPerHost = 40;' Penso che la seconda volta che intendi utilizzare l'oggetto 'backgroundSessionConfig', invece? –

+0

2015 e ancora nel conteggio dei riferimenti manuale ?! :) A proposito, ottima domanda e risposta, grazie. – Martin