Muszę przeprowadzić serię operacji pobierania i zapisywania bazy danych w mojej aplikacji. Używam NSOperation
i NSOperationQueue
do tego samego.
To jest scenariusz aplikacji:
- Pobierz wszystkie kody pocztowe z miejsca.
- Dla każdego kodu pocztowego pobierz wszystkie domy.
- Dla każdego domu należy pobrać dane dotyczące mieszkańców
Jak powiedziałem, zdefiniowałem NSOperation
dla każdego zadania. W pierwszym przypadku (Zadanie1) wysyłam do serwera żądanie pobrania wszystkich kodów pocztowych. Delegat w ramach NSOperation
otrzyma dane. Dane te są następnie zapisywane w bazie danych. Operacja na bazie danych jest zdefiniowana w innej klasie. Z NSOperation
klasy wykonuję wywołanie funkcji zapisu zdefiniowanej w klasie bazy danych.
Moje pytanie brzmi, czy operacja zapisu do bazy danych występuje w wątku głównym, czy w wątku w tle? Ponieważ wywoływałem go w ramach NSOperation
, spodziewałem się, że będzie działał w innym wątku (nie MainThread) jako NSOperation
. Czy ktoś może wyjaśnić ten scenariusz podczas pracy z NSOperation
i NSOperationQueue
.
NSOperation
nieNSOperationQueue
zawierają właściwości delegata.Odpowiedzi:
Jeśli utworzysz
NSOperationQueue
od podstaw, jak w:NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
Będzie w wątku w tle:
Chyba że używasz
mainQueue
:NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
Możesz również zobaczyć taki kod:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; [myQueue addOperationWithBlock:^{ // Background work [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // Main thread work (UI usually) }]; }];
Oraz wersja GCD:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { // Background work dispatch_async(dispatch_get_main_queue(), ^(void) { // Main thread work (UI usually) }); });
NSOperationQueue
daje lepszą kontrolę nad tym, co chcesz robić. Możesz tworzyć zależności między dwiema operacjami (pobieranie i zapisywanie w bazie danych). Aby przekazać dane między jednym blokiem a drugim, możesz założyć na przykład, żeNSData
będzie pochodzić z serwera, więc:__block NSData *dataFromServer = nil; NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init]; __weak NSBlockOperation *weakDownloadOperation = downloadOperation; [weakDownloadOperation addExecutionBlock:^{ // Download your stuff // Finally put it on the right place: dataFromServer = .... }]; NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init]; __weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation; [weakSaveToDataBaseOperation addExecutionBlock:^{ // Work with your NSData instance // Save your stuff }]; [saveToDataBaseOperation addDependency:downloadOperation]; [myQueue addOperation:saveToDataBaseOperation]; [myQueue addOperation:downloadOperation];
Edycja: Dlaczego używam
__weak
referencji dla operacji, można znaleźć tutaj . Ale w skrócie należy unikać zachowywania cykli.źródło
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
i że NSOperationQueue w głównym wątku nie może zostać zawieszone.Jeśli chcesz wykonać operację zapisu do bazy danych w wątku w tle, musisz utworzyć
NSManagedObjectContext
dla tego wątku.Tło można utworzyć
NSManagedObjectContext
w metodzie startowej odpowiedniejNSOperation
podklasy.Sprawdź dokumentację Apple pod kątem współbieżności z danymi podstawowymi.
Możesz również utworzyć,
NSManagedObjectContext
który wykonuje żądania we własnym wątku w tle, tworząc go za pomocąNSPrivateQueueConcurrencyType
i wykonując żądania wewnątrz swojejperformBlock:
metody.źródło
Z NSOperationQueue
Więc,
[NSOperationQueue mainQueue] // added operations execute on main thread [NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
W twoim przypadku możesz chcieć stworzyć swój własny "wątek bazy danych" przez tworzenie podklas
NSThread
i wysyłanie do niego wiadomości za pomocąperformSelector:onThread:
.źródło
Wątek wykonania NSOperation zależy od
NSOperationQueue
miejsca dodania operacji. Spójrz na to stwierdzenie w swoim kodzie -[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class
Wszystko to zakłada, że nie wykonałeś żadnego dalszego wątku, którego
main
metodaNSOperation
jest prawdziwym potworem, w którym napisałeś instrukcje pracy (spodziewasz się).Jednak w przypadku operacji równoległych scenariusz jest inny. Kolejka może utworzyć wątek dla każdej współbieżnej operacji. Chociaż nie jest to gwarantowane i zależy od zasobów systemowych w porównaniu z zapotrzebowaniem na zasoby operacyjne w tym punkcie systemu. Możesz kontrolować współbieżność kolejki operacji przez to
maxConcurrentOperationCount
właściwości.EDYTUJ -
Uważam, że Twoje pytanie jest interesujące i sam przeprowadziłem analizę / logowanie. Mam NSOperationQueue utworzony w głównym wątku w ten sposób -
self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease]; NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]); self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency
Następnie utworzyłem NSOperation i dodałem go za pomocą addOperation. W głównej metodzie tej operacji, gdy sprawdziłem bieżący wątek,
NSLog(@"Operation obj = %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);
nie był to główny wątek. Okazało się, że bieżący obiekt wątku nie jest głównym obiektem wątku.
Tak więc niestandardowe tworzenie kolejki w głównym wątku (bez współbieżności między jej operacjami) niekoniecznie oznacza, że operacje będą wykonywane szeregowo w samym wątku głównym.
źródło
[[NSOperationQueue alloc] init]
gwarancji, że kolejka będzie używać podstawowego domyślnego globalnego DispatchQueue w tle. Tak więc każda niestandardowa zainicjowana OperationQueue będzie przekazywać swoje zadania do DispatchQueues w tle, a tym samym przekazywać zadania do wątków, które nie są głównym wątkiem. Aby wprowadzić operacje do głównego wątku, musisz uzyskać główną OperationQueue za pomocą czegoś podobnego[NSOperationQueue mainQueue]
. W ten sposób uzyskuje się główną kolejkę operacji, która wewnętrznie używa głównej DispatchQueue, a zatem ostatecznie przekazuje zadania do głównego wątku.Podsumowanie z dokumentacji to
operations are always executed on a separate thread
(po iOS 4 sugeruje kolejki operacji bazowych GCD).Sprawdzenie, czy rzeczywiście działa w wątku innym niż główny, jest trywialne:
NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");
Podczas uruchamiania w wątku trywialne jest użycie GCD / libdispatch do uruchomienia czegoś w głównym wątku, niezależnie od tego, czy chodzi o dane podstawowe, interfejs użytkownika czy inny kod wymagany do uruchomienia w głównym wątku:
dispatch_async(dispatch_get_main_queue(), ^{ // this is now running on the main thread });
źródło
Jeśli robisz nietrywialne wątki, powinieneś użyć FMDatabaseQueue .
źródło