2013-09-22 21 views
15

Così sto creando il mio download sul thread principaleDiscussioni NSURLSession: Monitoraggio download multipli sfondo

NSURLRequest *request = [NSURLRequest requestWithURL:download.URL]; 
NSURLSessionDownloadTask *downloadTask = [self.downloadSession downloadTaskWithRequest:request]; 
[downloadTask resume]; 

e aggiungendo la NSManagedContextID associato con il download a un NSMutableDictionary, così posso recuperare in un secondo momento nel delegato call-back

[self.downloads setObject:[download objectID] forKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]]; 

mio self.downloadSession sopra è configurato in questo modo

- (NSURLSession *)backgroundSession 
{ 
static NSURLSession *session = nil; 
static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{ 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.test.backgroundSession"]; 
    configuration.discretionary = YES; 
    session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; 
}); 
return session; 
} 

Il mio problema è i callback delegato sembrano essere invitato diversi thread

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 
{ 
    NSManagedObjectID *downloadID = [self.downloads objectForKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]]; 

    double progress = (double)totalBytesWritten/(double)totalBytesExpectedToWrite; 

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:downloadID,@"download",[NSNumber numberWithDouble:progress],@"progress", nil]; 

    [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:nil userInfo:userInfo]; 

} 

Così, quando accedo self.downloads per ottenere l'objectID corretto, in realtà sto accesso alla NSMutableDictionary da un thread diverso da quello è stato creato su, e credo che NSMutableDictionary non sia sicuro. Allora, qual è la soluzione migliore per questo, ho potuto usato qualcosa di simile

session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; 

quando si dichiara la sessione, impostare la coda delegato al mainQueue che causa tutti i delegati per essere chiamato sul thread principale, ma vorrei come mantenere tutti i callback su un thread in background, se possibile

+0

Hai provato a impostare il tuo thread durante la definizione della sessione? – Mundi

+0

@Mundi Per quanto riguarda la gestione con 'NSMutableArray' e il modello di' NSObject' come spiegato in [questo tutorial?] (Http://www.appcoda.com/background-transfer-service-ios7/) – Praveenkumar

risposta

10

Nell'esempio non si tratta di un problema, poiché il dizionario viene consegnato al sistema di notifica e non viene più utilizzato dal thread della coda di operazioni. La sicurezza del thread è solo un problema quando un oggetto è potenzialmente accessibile da più thread contemporaneamente.

Se il dict sarebbe un iVar si dovrebbe fare in questo modo:

Crea la tua coda come questo

myQueue = [[NSOperationQueue alloc] init]; 
// This creates basically a serial queue, since there is just on operation running at any time. 
[myQueue setMaxConcurrentOperationCount:1]; 

quindi pianificare ogni accesso al dizionario su questa coda, in questo modo, per esempio :

[myQueue addOperationWithBlock:^ 
{ 
    // Access your dictionary 
}]; 

E naturalmente utilizzare questa coda per la vostra delegazione URLSesson:

session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:myQueue]; 

Poiché questa coda è impostata come una coda seriale, ci sarà sempre un solo thread che accede al dict in background.

Fare attenzione quando si calcola qualcosa con le informazioni dict. Devi farlo anche in quella coda. Tuttavia, è possibile inserire il risultato del calcolo su qualsiasi altra coda/thread, ad esempio per aggiornare l'interfaccia utente sul thread principale.

[myQueue addOperationWithBlock:^ 
{ 
    // Calculate with your dictionary 
    // Maybe the progress calcualtion 
    NSString* progress = [self calculateProgress: iVarDict]; 
    dispatch_async(dispatch_get_main_queue(),^
    { 
     // use progress to update UI 
    }); 
}]; 

Penso che per la pubblicazione di una notifica non è necessario utilizzare tale modello, perché il sistema gestisce correttamente la filettatura. Ma per essere salvati dovresti controllare questo.

+0

Bene, la documentazione Dice che non lo è: [Threading Prgramming Guide] (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html) – sofacoder

+0

E riguardo la gestione con 'NSMutableArray' e' NSObject' Il modello come spiegato in [questo tutorial?] (http://www.appcoda.com/background-transfer-service-ios7/) – Praveenkumar

0

È possibile utilizzare una coda seriale GCD per garantire che un solo delegato sia in esecuzione contemporaneamente.

Si può dichiarare la coda come una variabile della classe istanza e inizializzare nel metodo init, in questo modo:

dispatch_queue_t delegateQueue; 

...

delegateQueue = dispatch_queue_create("com.yourcompany.mydelegatequeue", 0x0); 

e nel metodo delegato, semplicemente eseguilo in questa coda:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 
{ 
    dispatch_sync(delegateQueue, ^{ 
    NSManagedObjectID *downloadID = [self.downloads objectForKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]]; 

    double progress = (double)totalBytesWritten/(double)totalBytesExpectedToWrite; 

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:downloadID,@"download",[NSNumber numberWithDouble:progress],@"progress", nil]; 

    [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:nil userInfo:userInfo]; 
}); 

} 

In questo modo, anche se ogni delegato è chiamato i Nel suo thread, c'è solo l'accesso a self.download in una volta sola, e puoi tenerli in thread separati.

+0

Questo è leggermente sub-ottimale, poiché come indicato nella documentazione NSURLSession crea una coda seriale quando delegateQueue è impostato su zero. Quindi la chiamata delegata viene eseguita su quella coda e quindi il codice passa immediatamente a un'altra coda. Una coda (e quindi almeno un thread è sprecato) che può essere facilmente evitato. È possibile impostare NSURLSession su mainQueue, ma di nuovo sarebbe stato il momento di una coda che non è necessaria. – sofacoder

+0

Poiché pubblica subito la notifica, sarebbe più intelligente avvolgere tutto nella spedizione sul thread principale. – Andy

Problemi correlati