2013-09-22 13 views
15

Więc tworzę moje pobrania w głównym wątkuNSURLSession wątki: Śledzenie wielu pobieranie tła

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

i dodanie NSManagedContextID związanego z pobraniem do NSMutableDictionary, więc mogę je odzyskać później w delegata Call-beki

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

My self.downloadSession powyżej jest skonfigurowany jak to

- (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; 
} 

Mój problem jest callbacks delegat wydaje się być wywołana na różnych wątkach

- (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]; 

} 

Więc kiedy dostęp self.downloads aby uzyskać prawidłowy objectID, ja faktycznie dostęp do NSMutableDictionary z innego wątku niż jeden został stworzony na i wierzę, że NSMutableDictionary nie jest bezpieczny dla wątków. Więc, co jest najlepszym rozwiązaniem dla tego, mogę stosować coś takiego

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

podczas deklarowania sesję ustawić kolejkę przekazać mainQueue co powoduje wszystkie delegatów na miano w głównym wątku, ale chciałbym jak zachować wszystkie wywołania zwrotne w wątku tła, jeśli to możliwe

+0

Czy próbowałeś po prostu ustawić własny wątek podczas definiowania sesji? – Mundi

+0

@Mundi A co z obsługą z 'NSMutableArray' i' modelem NSObject' jak wyjaśniono w [ten samouczek?] (Http://www.appcoda.com/background-transfer-service-ios7/) – Praveenkumar

Odpowiedz

10

W twoim przykładzie, który nie jest problemem, ponieważ słownik jest przekazywany do systemu powiadomień i nie jest ponownie używany przez wątek kolejki operacji. Bezpieczeństwo wątków jest problemem tylko wtedy, gdy obiekt jest potencjalnie dostępny z wielu wątków w tym samym czasie.

Jeśli DICT byłoby Ivar należy zrobić to w ten sposób:

Stwórz własną kolejkę tak

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

następnie zaplanować każdą Dostęp do słownika w tej kolejce, jak to na przykład :

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

I oczywiście skorzystać z tej kolejki do przekazania URLSesson:

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

Ponieważ ta kolejka jest skonfigurowana jako kolejka szeregowa, zawsze będzie tylko jeden wątek uzyskujący dostęp do dyktatury w tle.

Należy zachować ostrożność, obliczając wartość za pomocą informacji o dyktafonie. Musisz to zrobić również w tej kolejce. Możesz jednak umieścić wynik swoich obliczeń w dowolnej innej kolejce/wątku, na przykład, aby zaktualizować interfejs w głównym wątku.

[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 
    }); 
}]; 

Myślę, że do wysłania powiadomienia nie trzeba używać tego wzorca, ponieważ system poprawnie obsługuje wątki. Ale żeby zostać zbawionym, powinieneś to sprawdzić.

+0

Cóż, dokumentacja mówi, że nie jest: [Threading Prgramming Guide] (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html) – sofacoder

+0

Co z obsługą 'NSMutableArray' i' NSObject' model taki jak wyjaśniono w [ten samouczek?] (http://www.appcoda.com/background-transfer-service-ios7/) – Praveenkumar

0

Można użyć kolejki szeregowej GCD, aby zapewnić wykonywanie tylko jednego delegata jednocześnie.

Można zadeklarować kolejkę jako zmiennej instancji swojej klasie i zainicjować go w metodzie init tak:

dispatch_queue_t delegateQueue; 

...

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

w swojej metodzie delegata po prostu wykonaj go w tej kolejce:

- (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]; 
}); 

} 

W ten sposób, chociaż każdy uczestnik jest nazywany W swoim wątku, tylko oni uzyskują dostęp do self.downloads w tym samym czasie i możesz zachować je w osobnych wątkach.

+0

To jest nieco suboptymalne, ponieważ jak stwierdzono w dokumentacji NSURLSession tworzy kolejkę szeregową, gdy delegateQueue jest ustawione na zero. Tak więc wywołanie delegate jest wykonywane w tej kolejce, a następnie kod natychmiast przechodzi do kolejnej kolejki. Jedna kolejka (i co najmniej jeden wątek jest marnowana), której można łatwo uniknąć. Możesz ustawić NSURLSession do mainQueue, ale to znowu był czas, który jest kolejką, która nie jest konieczna. – sofacoder

+0

Ponieważ od razu wysyła powiadomienia, lepiej byłoby zawinąć całość w wysyłkę głównego wątku. – Andy

Powiązane problemy