DispatchQueue to fundamentalna kolejka Grand Central Dispatch (GCD) do zarządzania zadaniami asynchronicznymi w iOS i macOS. Według Apple Developer Documentation, 2026, DispatchQueue abstrahuje zarządzanie wątkami od programisty poprzez kolejki serial i concurrent. GCD automatycznie rozdziela zadania do systemowej puli wątków, eliminując ręczne tworzenie i niszczenie wątków.
Najważniejsze
DispatchQueue to obiekt frameworka Grand Central Dispatch (GCD), który zarządza wykonywaniem zadań w systemowych lub niestandardowych kolejkach wątków. Grand Central Dispatch to niskopoziomowa biblioteka Apple, dostępna od iOS 4 i macOS 10.6, która całkowicie abstrahuje zarządzanie wątkami od programisty. GCD używa puli wątków systemu operacyjnego i automatycznie skaluje liczbę wątków pod obciążenie urządzenia.
Programista nie musi ręcznie tworzyć i niszczyć wątków — GCD przejmuje to zadanie, udostępniając prosty API przez DispatchQueue. Zadanie w postaci domknięcia (closure) jest wysyłane do kolejki przez metody sync lub async. W pierwszym przypadku wątek wywołujący jest blokowany do zakończenia zadania, w drugim — kontynuuje wykonywanie natychmiast.
Według Apple (2026), GCD używa systemowej puli wątków, która dostosowuje się do liczby rdzeni i bieżącego obciążenia procesora. Kolejka concurrent nie tworzy nowego wątku dla każdego zadania — GCD ponownie wykorzystuje wątki z puli, co minimalizuje narzut na tworzenie wątków.
Grand Central Dispatch składa się z trzech kluczowych komponentów: kolejki (DispatchQueue), grupy (DispatchGroup) i semafory (DispatchSemaphore). Kolejka to podstawowy element, przyjmujący zadania w postaci bloków kodu. DispatchGroup synchronizuje wykonywanie wielu zadań, a DispatchSemaphore ogranicza dostęp do wspólnego zasobu określoną liczbą wątków.
Każda kolejka GCD jest powiązana z określoną klasą QoS (Quality of Service), która informuje system o ważności zadania. System używa QoS do rozdzielania czasu procesora między kolejki, nadając priorytet bardziej krytycznym zadaniom — na przykład aktualizacji UI lub obsłudze dotknięć użytkownika.
Kolejka Serial wykonuje zadania ściśle sekwencyjnie, jedno po drugim. Jeśli do kolejki serial zostaną umieszczone trzy zadania, drugie rozpocznie się dopiero po całkowitym zakończeniu pierwszego. Kolejki serial są używane do synchronizacji dostępu do wspólnych zasobów — na przykład do tablicy, która jest modyfikowana z kilku części kodu.
Kolejka Concurrent uruchamia kilka zadań jednocześnie, rozdzielając je między dostępne wątki z systemowej puli. Zadania w kolejce concurrent są uruchamiane w kolejności dodania (FIFO), ale kończą się w dowolnej kolejności, jeśli czas ich wykonania jest różny. Kolejka Concurrent nie gwarantuje kolejności zakończenia — tylko kolejności uruchomienia.
| Parametr | Kolejka Serial | Kolejka Concurrent |
|---|---|---|
| Kolejność wykonywania | Ściśle sekwencyjna | Równoległa |
| Liczba wątków | Jeden | Kilka z puli GCD |
| Zastosowanie | Ochrona współdzielonych zasobów | Niezależne obliczenia |
| Main queue | Tak (główny wątek) | Nie |
| Ryzyko deadlocka | Wysokie przy sync na tej samej kolejce | Niskie |
Kolejka serial jest idealna do zadań modyfikujących wspólny stan — zapis do pliku, aktualizacja modelu danych lub praca z Core Data. Użycie kolejki serial gwarantuje, że dwa fragmenty kodu nie zmodyfikują tych samych danych jednocześnie, eliminując stan wyścigu bez dodatkowych blokad.
Kolejka Concurrent nadaje się do zadań niezależnych od siebie: ładowanie wielu obrazów, równoległe żądania sieciowe lub przetwarzanie wsadowe danych. GCD automatycznie decyduje, ile zadań uruchomić jednocześnie, na podstawie liczby rdzeni procesora i bieżącego obciążenia systemu.
QoS (Quality of Service) — mechanizm GCD informujący system operacyjny o ważności i pilności zadania. System używa QoS do planowania wątków: zadania z wyższym QoS otrzymują więcej czasu procesora i są uruchamiane wcześniej. Wartość QoS jest przekazywana przy tworzeniu kolejki lub wysyłaniu konkretnego zadania.
W GCD dostępnych jest pięć klas QoS. .userInteractive — najwyższy priorytet dla zadań związanych z UI. .userInitiated — dla zadań inicjowanych przez użytkownika. .utility — dla zadań w tle z wyświetlaniem postępu. .background — dla zadań niewidocznych dla użytkownika. .default — poziom pośredni między userInitiated a utility, używany domyślnie.
Według Apple (2026), nieprawidłowy wybór QoS jest jedną z częstych przyczyn problemów z wydajnością. Uruchomienie tła z QoS .userInteractive odbiera zasoby interfejsowi UI, powodując mikro-opóźnienia w animacjach. Zaleca się wybór najniższego QoS, który wciąż zapewnia akceptowalny czas wykonania.
Podczas ładowania obrazu do natychmiastowego wyświetlenia użyj .userInitiated — użytkownik oczekuje wyniku. Do wstępnego ładowania następnego ekranu wystarczy .utility. Synchronizacja w tle z serwerem jest wykonywana z .background, minimalizując wpływ na aktywne zadania.
DispatchGroup pozwala śledzić zakończenie grupy zadań. Gdy wszystkie zadania w grupie zostaną zakończone, GCD wywołuje handler notify na określonej kolejce. Jest to szczególnie przydatne przy ładowaniu kilku niezależnych zasobów — danych profilu, listy znajomych i ustawień, gdy interfejs musi zostać zaktualizowany dopiero po otrzymaniu wszystkich danych.
DispatchGroup obsługuje synchroniczne wywołanie wait(), które blokuje bieżący wątek do zakończenia wszystkich zadań. Jest to wygodne, gdy kod nie może kontynuować bez wyników grupy. Wariant asynchroniczny — notify() — wywołuje domknięcie na określonej kolejce po zakończeniu wszystkich zadań, nie blokując wątku wywołującego.
DispatchSemaphore kontroluje dostęp do zasobu, ograniczając liczbę jednoczesnych dostępów. Semafory z początkową wartością 3 pozwalają uruchomić nie więcej niż trzy równoległe zadania. Przy wywołaniu wait() licznik zmniejsza się, przy signal() — zwiększa. Jeśli licznik wynosi zero, wątek jest blokowany do zwolnienia zasobu.
Rozważmy trzy praktyczne przykłady użycia DispatchQueue w Swift. Pierwszy demonstruje podstawowe wywołanie async z powrotem na główny wątek, drugi — synchronizację przez kolejkę serial, trzeci — DispatchGroup dla równoległych żądań.
DispatchQueue.main to kolejka serial głównego wątku, przeznaczona wyłącznie do operacji UI. Zawsze używaj jej do aktualizacji interfejsu po zakończeniu pracy w tle.
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
let data = self.fetchData()
DispatchQueue.main.async {
self.updateUI(with: data)
}
}
Utworzenie własnej kolejki serial z unikalnym identyfikatorem synchronizuje dostęp do zmiennej tablicy. Wszystkie operacje odczytu i zapisu przechodzą przez jedną kolejkę, eliminując stan wyścigu.
let serialQueue = DispatchQueue(label: "com.app.items")
var items: [Int] = []
serialQueue.async {
items.append(1)
}
serialQueue.async {
let last = items.last
DispatchQueue.main.async {
print("Last item: \(last)")
}
}
DispatchGroup pozwala uruchomić wiele zadań na kolejce concurrent i otrzymać powiadomienie o zakończeniu wszystkich. Jest to przydatne przy ładowaniu danych dla ekranu profilu.
let group = DispatchGroup()
let worker = DispatchQueue.global()
worker.async(group: group) { self.loadProfile() }
worker.async(group: group) { self.loadFriends() }
worker.async(group: group) { self.loadSettings() }
group.notify(queue: DispatchQueue.main) {
self.showCompleteUI()
}
Deadlock przy wywołaniu sync na kolejce serial — najczęstszy błąd. Jeśli zadanie na kolejce serial wywołuje queue.sync na tej samej kolejce, wątek blokuje się na stałe. Kolejka czeka na zakończenie bieżącego zadania, a zadanie czeka na zakończenie wywołania sync — klasyczna blokada wzajemna.
Wszystkie operacje na UIKit muszą być wykonywane na głównym wątku. Xcode wykrywa takie błędy w trybie Debug przez Main Thread Checker. W kompilacji Release prowadzą one do nieprzewidywalnego zachowania: animacje nie uruchamiają się, UI nie aktualizuje się, możliwe są crash'e.
Tworzenie setek niestandardowych kolejek zamiast globalnych — anty wzorzec. Każda kolejka zużywa zasoby systemowe. Do większości zadań wystarczą globalne kolejki concurrent z różnym QoS i jedna-dwie kolejki serial do synchronizacji współdzielonych danych.
Podczas wykonywania zasobożernych zadań cyklicznych na kolejce tła bez autoreleasepool pamięć rośnie do zakończenia całej pętli. ARC zwalnia obiekty dopiero przy wyjściu z autorelease pool. Umieszczaj iteracje pętli w autoreleasepool { }, aby zapewnić terminowe zwalnianie pamięci.
Często zadawane pytania
OperationQueue jest zbudowana na GCD, ale udostępnia API wyższego poziomu z zależnościami operacji, KVO i obsługą anulowania. DispatchQueue to niskopoziomowa kolejka do prostych zadań async bez zarządzania zależnościami.
GCD nie obsługuje zatrzymania uruchomionego zadania. Metoda suspend() wstrzymuje tylko nowe zadania, bieżące wykonuje się do końca. Do anulowania potrzebne jest ręczne sprawdzanie flagi wewnątrz kodu zadania.
Dla podstawowego żądania z natychmiastowym wyświetleniem wyniku — .userInitiated. Do wstępnego ładowania danych — .utility. Do synchronizacji w tle — .background.
GCD nie ustala stałej liczby wątków. Pula wątków dynamicznie skaluje się pod obciążeniem, uwzględniając rdzenie procesora, bieżące obciążenie i QoS każdego zadania. Maksymalna liczba jest ograniczona przez system.
UIKit nie jest bezpieczny wątkowo — wszystkie jego klasy muszą być wywoływane tylko z głównego wątku. Naruszenie powoduje nieprzewidywalne zachowanie, pominięte aktualizacje i crash'e w produkcji.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również