Async/Await — to para kluczowych słów do pisania kodu asynchronicznego w stylu synchronicznym, dostępna w Swift od iOS 13 i w Kotlin z korutynami. Według Apple Swift Documentation, 2026, Async/Await zastępuje łańcuchy callbacków i GCD, czyniąc kod asynchroniczny liniowym i czytelnym. Słowo kluczowe async oznacza funkcję jako asynchroniczną, a await wstrzymuje jej wykonanie do momentu otrzymania wyniku.
Najważniejsze
Async/Await — to konstrukcja językowa, która pozwala pisać kod asynchroniczny tak liniowo, jak synchroniczny. W Swift pojawiła się w iOS 13 / macOS 10.15 wraz z frameworkiem Swift Concurrency. W Kotlin async/await jest dostępny przez bibliotekę korutyn (kotlinx.coroutines) od wersji 1.3.
Przed pojawieniem się async/await kod asynchroniczny opierał się na callbackach, GCD lub RxSwift. Łańcuchy zagnieżdżonych callbacków prowadziły do callback hell — głębokiego zagnieżdżenia utrudniającego czytanie i debugowanie. Async/Await rozwiązuje ten problem, pozwalając funkcjom asynchronicznym „wstrzymywać się” w punktach await i wznawiać po otrzymaniu wyniku.
Według danych Google (2025), użycie async/await w Kotlin zmniejsza liczbę linii kodu asynchronicznego o 40–60% w porównaniu z podejściem callbackowym. W Swift adopcja Swift Concurrency osiągnęła 68% wśród opublikowanych aplikacji w 2025 roku. iOS 16 dodało obsługę Swift Concurrency w UIKit, SwiftUI i Foundation, co uczyniło async/await standardowym narzędziem dla wszystkich nowych projektów Apple.
Kluczowa różnica między async/await a wątkami — wstrzymanie bez blokowania. Gdy funkcja napotyka await, wstrzymuje się, zwalniając bieżący wątek dla innych zadań. Po zakończeniu operacji asynchronicznej funkcja wznawia się na tym samym lub innym wątku. Nazywa się to cooperative multitasking.
Funkcja async jest kompilowana do maszyny stanów — automatu skończonego zarządzającego stanami wykonania. W punkcie await kompilator zapisuje kontekst (zmienne lokalne, adres powrotu) i przekazuje sterowanie do runtime. Gdy wynik jest gotowy, runtime przywraca kontekst i kontynuuje wykonanie.
W Swift ta maszyna stanów jest zaimplementowana na poziomie kompilatora SIL (Swift Intermediate Language). W Kotlin — poprzez mechanizm Continuation Passing Style (CPS): każda funkcja suspend przyjmuje ukryty parametr Continuation, przez który zwracany jest wynik. Oba podejścia gwarantują bezpieczne wznowienie bez wycieków pamięci.
Podejście callbackowe wymaga przekazania domknięcia, które jest wywoływane po zakończeniu operacji. Przy trzech kolejnych żądaniach powstają trzy poziomy zagnieżdżenia z obsługą błędów na każdym. Async/Await zamienia te same trzy żądania w trzy kolejne linie z jednym blokiem catch.
GCD (DispatchQueue) rozwiązuje problem callback hell poprzez kolejki serial i DispatchGroup, ale pozostaje rozwlekły. Dla prostego zadania — załadować user, potem friends, potem UI — wymaga tworzenia kolejek, grup i bloków notify. Async/Await wykonuje to samo zadanie trzema liniami w jednej funkcji.
| Aspekt | Callback | GCD | Async/Await |
|---|---|---|---|
| Zagnieżdżenie | Głębokie (piramida zagłady) | Średnie (grupy+notify) | Liniowe (płaskie) |
| Obsługa błędów | Per-callback | Ręczna | Jednolita (try/catch) |
| Anulowanie | Ręczne | Ograniczone | Task.cancel() |
| Thread safety | Ręczna | Przez serial queue | Przez MainActor |
Według Apple WWDC 2024, Swift Concurrency async/await to zalecane podejście dla nowego kodu. GCD pozostaje do integracji z bibliotekami C i specyficznych scenariuszy niskopoziomowego przetwarzania wątków.
Task — to podstawowa jednostka pracy asynchronicznej w Swift. Task jest tworzony w kontekście istniejącego aktora lub na dowolnym wątku. Wewnątrz Task można wywoływać funkcje async przez await. TaskGroup pozwala uruchomić kilka zadań potomnych równolegle i zebrać ich wyniki — odpowiednik DispatchGroup na sterydach.
Actor — bezpieczny wątkowo typ referencyjny, chroniący swój stan przed wyścigami danych. Kompilator gwarantuje, że dostęp do właściwości aktora jest możliwy tylko przez wywołania async lub w ramach izolacji aktora. Actor zastępuje serial DispatchQueue do ochrony współdzielonego stanu modyfikowalnego bez ręcznych blokad.
Zasada Structured Concurrency mówi: każde zadanie asynchroniczne ma rodzica, a rodzic nie kończy się, dopóki nie zakończą się wszystkie jego dzieci. Jeśli zadanie A uruchamia Task { await B() }, to A oczekuje na B. Zapobiega to wyciekom zadań i zapewnia przewidywalny cykl życia.
W Kotlin async/await jest zaimplementowany przez coroutines — lekkie wątki języka. Funkcja jest oznaczana słowem kluczowym suspend (odpowiednik Swift async). Do uruchomienia używa się launch (fire-and-forget) lub async (z wynikiem). Await jest wywoływany w celu uzyskania wyniku z async.
Dispatcher określa, na której puli wątków wykonywana jest korutyna: Dispatchers.Main dla UI, Dispatchers.IO dla sieci/dysku i Dispatchers.Default dla zadań intensywnych obliczeniowo. Structured Concurrency jest zapewniana przez CoroutineScope — po anulowaniu scope anulowane są wszystkie korutyny potomne.
Według danych JetBrains (2025), 97% aplikacji Android w Google Play używa kotlinx.coroutines, a 82% z nich stosuje async/await do zapytań sieciowych. Coroutines stały się standardem de facto dla programowania asynchronicznego w Android. Kotlin Multiplatform również wspiera korutyny, umożliwiając używanie wspólnego kodu asynchronicznego na Android, iOS i po stronie serwera.
Rozważmy po dwa przykłady dla każdego języka: sekwencyjne ładowanie danych i równoległe żądania z TaskGroup lub async/await.
await wstrzymuje wykonanie do momentu pobrania danych użytkownika, następnie — do pobrania znajomych. Żadnych zagnieżdżonych domknięć.
func loadProfile() async throws -> Profile {
let user = try await api.fetchUser()
let friends = try await api.fetchFriends(for: user.id)
return Profile(user: user, friends: friends)
}
TaskGroup uruchamia zadania równolegle i zbiera wyniki w tablicy. Kolejność wyników może nie pokrywać się z kolejnością uruchamiania.
func loadParallel() async throws -> [String] {
await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await api.fetchName() }
group.addTask { try await api.fetchEmail() }
group.addTask { try await api.fetchAvatar() }
var results = [String]()
for try await value in group { results.append(value) }
return results
}
}
Funkcje suspend mogą wywoływać inne funkcje suspend. Odpowiednik Swift: sekwencyjne wywołania.
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
W Kotlin równoległość osiąga się przez coroutineScope z async dla każdego żądania.
suspend fun loadParallel(): List<String> = coroutineScope {
val name = async { api.fetchName() }
val email = async { api.fetchEmail() }
val avatar = async { api.fetchAvatar() }
listOf(name.await(), email.await(), avatar.await())
}
Zapomniany Task { } — funkcji asynchronicznej nie można wywołać z kontekstu synchronicznego bez opakowania w Task. W Swift skutkuje to błędem kompilacji. W Kotlin wymagane jest uruchomienie przez lifecycleScope lub viewModelScope. Próba wywołania funkcji suspend ze zwykłej funkcji nie skompiluje się.
Nie każdą funkcję trzeba czynić asynchroniczną. Zadania intensywne obliczeniowo bez I/O nie zyskują na async — lepiej wykonywać je na DispatchQueue z .userInitiated QoS. Async/Await jest zoptymalizowany dla operacji I/O-bound: sieć, dysk, oczekiwanie.
Użycie .result na głównym wątku (synchroniczne oczekiwanie na wynik funkcji async) prowadzi do blokowania. W Swift Task.synchronousWait nie jest zalecane przez Apple. W Kotlin runBlocking na głównym wątku to antypattern — używaj lifecycleScope.
Jeśli Task nie jest zapisany we właściwości klasy, po wyjściu z kontekstu zostaje anulowany. W Swift Task jest scoped — jego cykl życia jest powiązany z kontekstem tworzącym. Zachowuj Task we właściwości przy potrzebie długotrwałej operacji.
Często zadawane pytania
Async/Await — to konstrukcja językowa, niewymagająca tworzenia kolejek. DispatchQueue — to systemowe API do zarządzania wątkami. Async/await jest kompilowany do maszyny stanów na poziomie języka, GCD — do syscall na poziomie systemu operacyjnego.
Nie — await wstrzymuje funkcję, ale zwalnia wątek dla innych zadań. Po zakończeniu operacji funkcja wznawia się na dowolnym dostępnym wątku z puli.
Tak — przez Task.cancel() w Swift lub Job.cancel() w Kotlin. Anulowanie rozprzestrzenia się na wszystkie zadania potomne zgodnie z Structured Concurrency. Kod sam sprawdza flagę anulowania przez Task.isCancelled lub ensureActive().
MainActor — to aktor, którego właściwości i metody są zawsze wykonywane na głównym wątku. Oznaczając funkcję @MainActor, gwarantujesz, że aktualizacje UI odbywają się na właściwym wątku, zastępując DispatchQueue.main.async.
Async/Await jest dostępny w Swift (iOS 13+), Kotlin (przez kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust i Go (przez goroutines z syntaktycznym cukrem).
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ż