Alternatywa dla dispatch_get_current_queue () dla bloków uzupełniania w iOS 6?

101

Mam metodę, która akceptuje blok i blok uzupełniania. Pierwszy blok powinien działać w tle, a blok uzupełniania w dowolnej kolejce, w której wywołano metodę.

W przypadku tego ostatniego zawsze używałem dispatch_get_current_queue(), ale wygląda na to, że jest przestarzały w iOS 6 lub nowszym. Czego powinienem użyć zamiast tego?

cfischer
źródło
dlaczego, jak twierdzisz, dispatch_get_current_queue()jest przestarzały w iOS 6?
doktorzy
3
Kompilator narzeka na to. Spróbuj.
cfischer
4
@jere Sprawdź plik nagłówkowy, stwierdza, że ​​jest pozbawiony
WDUK
Oprócz dyskusji na temat tego, co jest najlepszą praktyką, widzę [NSOperationQueue currentQueue], które może odpowiedzieć na pytanie. Nie jestem pewien co do zastrzeżeń co do jego użycia.
Matt,
znalezione ostrzeżenie ------ [NSOperationQueue currentQueue] nie to samo co dispatch_get_current_queue () ----- Czasami zwraca wartość null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) to% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) to% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) jest <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) jest (null) ----- zdezprogramowany lub nie dispatch_get_current_queue () wydaje się być jedynym rozwiązaniem jakie widzę do raportowania aktualnej kolejki w każdych warunkach
godzilla

Odpowiedzi:

64

Wzorzec „uruchamiaj w dowolnej kolejce, w której był dzwoniący” jest atrakcyjny, ale ostatecznie nie jest to świetny pomysł. Ta kolejka może być kolejką o niskim priorytecie, kolejką główną lub inną kolejką o nieparzystych właściwościach.

Moim ulubionym podejściem jest stwierdzenie, że „blok uzupełniania działa na kolejce zdefiniowanej w ramach implementacji z następującymi właściwościami: x, y, z” i pozwolenie blokowi na wysłanie do określonej kolejki, jeśli wywołujący chce mieć większą kontrolę. Typowy zestaw właściwości do określenia to coś w rodzaju „szeregowy, niewprowadzający ponownie i asynchroniczny w odniesieniu do każdej innej kolejki widocznej dla aplikacji”.

** EDYTOWAĆ **

Catfish_Man umieścił przykład w komentarzach poniżej, po prostu dodaję go do jego odpowiedzi.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
Catfish_Man
źródło
7
W zupełności zgadzam. Widać, że Apple zawsze to przestrzega; za każdym razem, gdy chcesz coś zrobić w głównej kolejce, zawsze musisz wysłać wiadomość do głównej kolejki, ponieważ Apple zawsze gwarantuje, że jesteś w innym wątku. Przez większość czasu czekasz na długo działający proces, aby zakończyć pobieranie / manipulowanie danymi, a następnie możesz przetwarzać je w tle bezpośrednio w bloku uzupełniania, a następnie tylko wklejać wywołania interfejsu użytkownika do bloku wysyłkowego w głównej kolejce. Poza tym zawsze dobrze jest podążać za oczekiwaniami, które stawia Apple, ponieważ programiści będą przyzwyczajeni do tego wzorca.
Jack Lawrence,
1
świetna odpowiedź ... ale
liczyłem
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) completeHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completeHandler);}}
Catfish_Man
(Dla zupełnie trywialnego przykładu)
Catfish_Man
3
W ogólnym przypadku nie jest to możliwe, ponieważ jest możliwe (i całkiem prawdopodobne) przebywanie w więcej niż jednej kolejce jednocześnie, dzięki dispatch_sync () i dispatch_set_target_queue (). Istnieje kilka podzbiorów przypadku ogólnego, które są możliwe.
Catfish_Man
27

Jest to zasadniczo niewłaściwe podejście do opisywanego interfejsu API. Jeśli interfejs API akceptuje blok i blok uzupełniania do uruchomienia, muszą być spełnione następujące fakty:

  1. „Blok do uruchomienia” powinien być uruchamiany w kolejce wewnętrznej, np. Kolejce prywatnej dla API, a zatem całkowicie pod kontrolą tego API. Jedynym wyjątkiem jest sytuacja, gdy interfejs API wyraźnie deklaruje, że blok zostanie uruchomiony w głównej kolejce lub w jednej z globalnych kolejek współbieżnych.

  2. Blok uzupełniania powinien zawsze być wyrażony jako krotka (kolejka, blok), chyba że te same założenia, co dla # 1 są prawdziwe, np. Blok uzupełniania zostanie uruchomiony na znanej globalnej kolejce. Blok uzupełniania powinien ponadto być wysyłany asynchronicznie w kolejce przekazanej.

To nie są tylko punkty stylistyczne, są one całkowicie konieczne, jeśli twoje API ma być bezpieczne przed zakleszczeniami lub innymi zachowaniami skrajnymi, które w przeciwnym razie spowodują zawieszenie cię kiedyś na najbliższym drzewie. :-)

jkh
źródło
11
Brzmi rozsądnie, ale z jakiegoś powodu nie jest to podejście przyjęte przez Apple w przypadku ich własnych interfejsów API: większość metod, które przyjmują blok uzupełniania, nie przyjmuje również kolejki ...
cfischer
2
To prawda, i aby nieco zmodyfikować moje poprzednie stwierdzenie, jeśli jest oczywiste, że blok uzupełniania zostanie uruchomiony w głównej kolejce lub globalnej kolejce współbieżnej. Zmienię odpowiedź, żeby wskazać tyle.
jkh
Komentując, że Apple nie przyjmuje takiego podejścia: Apple nie zawsze ma rację co do definicji. Właściwe argumenty są zawsze silniejsze niż jakikolwiek konkretny autorytet, co potwierdzi każdy naukowiec. Myślę, że powyższa odpowiedź mówi o tym bardzo dobrze z punktu widzenia odpowiedniej architektury oprogramowania.
Werner Altewischer,
14

Inne odpowiedzi są świetne, ale dla mnie odpowiedź jest strukturalna. Mam taką metodę, która jest na Singletonie:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

który ma dwie zależności, którymi są:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

i

typedef void (^simplest_block)(void); // also could use dispatch_block_t

W ten sposób scentralizuję wywołania, które mają być wysyłane w drugim wątku.

Dan Rosenstark
źródło
12

Przede wszystkim należy zachować ostrożność podczas korzystania z dispatch_get_current_queue. Z pliku nagłówkowego:

Zalecane tylko do debugowania i rejestrowania:

Kod nie może przyjmować żadnych założeń dotyczących zwróconej kolejki, chyba że jest to jedna z kolejek globalnych lub kolejka, którą sam utworzył. Kod nie może zakładać, że synchroniczne wykonanie w kolejce jest zabezpieczone przed zakleszczeniem, jeśli ta kolejka nie jest kolejką zwróconą przez dispatch_get_current_queue ().

Możesz zrobić jedną z dwóch rzeczy:

  1. Zachowaj odniesienie do kolejki, w której pierwotnie opublikowałeś (jeśli utworzyłeś ją przez dispatch_queue_create) i używaj tego od tej pory.

  2. Korzystaj z kolejek zdefiniowanych przez system za pośrednictwem dispatch_get_global_queuei śledź, z której korzystasz.

Skutecznie, choć wcześniej polegałeś na systemie, który śledzi kolejkę, w której się znajdujesz, będziesz musiał to zrobić samodzielnie.

WDUK
źródło
16
W jaki sposób możemy „zachować odniesienie do kolejki, w której pierwotnie wysłałeś wiadomość”, jeśli nie możemy użyć jej dispatch_get_current_queue()do sprawdzenia, która to kolejka? Czasami kod, który musi wiedzieć, w której kolejce działa, nie ma o nim żadnej kontroli ani wiedzy. Mam dużo kodu, który może (i powinien) być wykonywany w kolejce w tle, ale czasami trzeba zaktualizować GUI (pasek postępu itp.), I dlatego potrzebuję dispatch_sync () do głównej kolejki dla tych operacji. Jeśli już znajduje się w głównej kolejce, dispatch_sync () zablokuje się na zawsze. Refaktoryzacja kodu zajmie mi miesiące.
Abhi Beckert
3
Myślę, że NSURLConnection daje wywołania zwrotne zakończenia w tym samym wątku, z którego jest wywoływany. Czy używałby tego samego interfejsu API „dispatch_get_current_queue” do przechowywania kolejki, z której jest wywoływany, do użycia w czasie wywołania zwrotnego?
defactodeity
5

Firma Apple wycofała się dispatch_get_current_queue(), ale pozostawiła lukę w innym miejscu, więc nadal możemy uzyskać bieżącą kolejkę wysyłek:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

Działa to przynajmniej dla głównej kolejki. Pamiętaj, że ta underlyingQueuewłaściwość jest dostępna od iOS 8.

Jeśli potrzebujesz wykonać blok uzupełniania w oryginalnej kolejce, możesz również użyć OperationQueuebezpośrednio, tj. Bez GCD.

kelin
źródło
0

To jest odpowiedź dla mnie też. Więc opowiem o naszym przypadku użycia.

Mamy warstwę usług i warstwę interfejsu użytkownika (między innymi). Warstwa usług uruchamia zadania w tle. (Zadania manipulacji danymi, zadania CoreData, połączenia sieciowe itp.). Warstwa usług ma kilka kolejek operacji, aby zaspokoić potrzeby warstwy interfejsu użytkownika.

Warstwa UI polega na warstwie usług, aby wykonać swoją pracę, a następnie uruchomić blok ukończenia sukcesu. Ten blok może zawierać kod UIKit. Prostym przypadkiem użycia jest pobranie wszystkich wiadomości z serwera i ponowne załadowanie widoku kolekcji.

Tutaj gwarantujemy, że bloki przekazywane do warstwy usług są wysyłane w kolejce, w której usługa została wywołana. Ponieważ dispatch_get_current_queue jest przestarzałą metodą, używamy NSOperationQueue.currentQueue, aby uzyskać bieżącą kolejkę wywołującego. Ważna uwaga na temat tej nieruchomości.

Wywołanie tej metody spoza kontekstu działającej operacji zazwyczaj powoduje zwrócenie wartości nil.

Ponieważ zawsze wywołujemy nasze usługi w znanej kolejce (nasze kolejki niestandardowe i kolejka główna), działa to dobrze dla nas. Mamy przypadki, w których serviceA może wezwać serviceB, który może wezwać serviceC. Ponieważ kontrolujemy, skąd jest wykonywane pierwsze zgłoszenie serwisowe, wiemy, że pozostałe usługi będą podlegać tym samym regułom.

Zatem NSOperationQueue.currentQueue zawsze zwróci jedną z naszych kolejek lub MainQueue.

Kris Subramanian
źródło