@MainActor — co to jest, zastosowanie i cechy w asynchronicznym kodzie Swift

Autor: IT Sectr Opublikowano: 2026-03-19 Czas czytania: 8 min

@MainActor to globalny aktor w języku Swift, który gwarantuje wykonanie kodu na głównym wątku. Według Apple Developer, 2024, @MainActor automatyzuje przełączanie na główny wątek podczas pracy z UI, uwalniając programistę od ręcznego wywoływania DispatchQueue.main.async. Adnotacja pojawiła się w Swift 5.5 wraz z systemem async/await.

Najważniejsze

  • @MainActor — globalny aktor Swift gwarantujący wykonanie na głównym wątku.
  • System async/await — podstawa, na której zbudowane jest działanie @MainActor.
  • Adnotacja klasy automatycznie umieszcza wszystkie jej metody na głównym wątku.
  • W przeciwieństwie do DispatchQueue.main, @MainActor sprawdza wątek na poziomie kompilatora.
  • Aktualizacje UI — główny obszar zastosowania @MainActor w iOS-development.

Co to jest @MainActor?

@MainActor — to globalny aktor (global actor) w Swift, który łączy właściwości aktorów z gwarancją wykonania na głównym wątku aplikacji. Jest częścią systemu współbieżności Swift, wprowadzonego w Swift 5.5 wraz z async/await i strukturalną współbieżnością. Adnotacja pozwala programiście nie myśleć o ręcznym przełączaniu wątków i zmniejsza liczbę błędów UI.

Definicja i miejsce w Swift Concurrency

Aktor w Swift to typ referencyjny, izolujący swój stan i gwarantujący, że tylko jeden wątek może go modyfikować. @MainActor — to specjalny globalny aktor, którego wykonawcą jest główny wątek. Każdy kod oznaczony @MainActor jest wykonywany na głównym wątku — nawet jeśli został wywołany z zadania tła.

Przed pojawieniem się @MainActor programiści ręcznie przełączali się na główny wątek przez DispatchQueue.main.async. Było to źródło częstych błędów: programiści zapominali o przełączeniu, co prowadziło do crashy z powodu aktualizacji UI nie na głównym wątku. @MainActor rozwiązuje ten problem na poziomie systemu typów.

Przesłanki powstania

Źródłem większości błędów w aplikacjach iOS jest UI-niebezpieczeństwo — aktualizacja interfejsu z wątku tła. Apple wbudowało @MainActor w Swift Concurrency, aby przełączanie na główny wątek było automatyczne i sprawdzane przez kompilator, eliminując całą klasę błędów runtime.

Jak działa @MainActor?

Zasada działania @MainActor opiera się na systemie wykonawczym Swift Concurrency. Gdy wątek wywołuje funkcję oznaczoną @MainActor, planista zawiesza ją na bieżącym wykonawcy i wznawia na głównym wątku. Kompilator śledzi granice wywołania i gwarantuje bezpieczeństwo.

Wykonawca głównego wątku

Za wykonanie @MainActor odpowiada MainActor.shared — wykonawca powiązany z głównym wątkiem aplikacji. Gdy funkcja asynchroniczna jest oznaczona @MainActor, zawsze wznawia się na tym wykonawcy, niezależnie od tego, na którym wątku zostało uruchomione oryginalne zadanie.

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // bezpiecznie, MainActor gwarantuje główny wątek
    }
}

Dziedziczenie kontekstu aktora

Jeśli funkcja jest oznaczona @MainActor i wywołuje inną funkcję asynchroniczną, domyślnie dziedziczy kontekst aktora. Oznacza to, że wszystkie zagnieżdżone wywołania również są wykonywane na głównym wątku, chyba że określono inaczej. Kompilator śledzi to i zgłasza błąd przy próbie przekazania niezgodnego domknięcia.

@MainActor vs DispatchQueue.main

Porównanie @MainActor i DispatchQueue.main pomaga zrozumieć, dlaczego nowy mechanizm jest uważany za bezpieczniejszy i wygodniejszy, chociaż oba rozwiązują to samo zadanie — wykonanie kodu na głównym wątku.

Bezpieczeństwo na poziomie typów

@MainActor — to sprawdzenie na poziomie kompilatora. Jeśli próbujesz wywołać funkcję @MainActor z niebezpiecznego kontekstu, kompilator wyświetli ostrzeżenie lub błąd. DispatchQueue.main.async — to wywołanie runtime: kod się skompiluje, ale może spaść w runtime przy próbie aktualizacji UI z wątku tła.

Wydajność i nadmiarowość

DispatchQueue.main.async dodaje do kolejki blok, który może być wykonany z opóźnieniem. @MainActor z async/await wykonuje bezpośrednie przełączenie wykonawcy bez tworzenia zbędnych domknięć. Zmniejsza to narzut i czyni kod bardziej przewidywalnym pod względem czasu wykonania.

swift
// Stare podejście
DispatchQueue.main.async {
    self.updateUI()
}

// Nowe podejście z @MainActor
@MainActor
func updateUI() {
    // wykonywane na głównym wątku
    self.label.text = "Zaktualizowano"
}
Kryterium@MainActorDispatchQueue.main
Sprawdzeniekompilatorruntime
Składniaadnotacja (deklaratywna)wywołanie (imperatywne)
Narzutniski (przełączenie wykonawcy)średni (domknięcie + kolejka)
Testowalnośćwysoka (MainActor.shared można podmienić)niska (trudno mockować)

Użycie @MainActor w projektach iOS

W rzeczywistych projektach iOS @MainActor jest stosowany w warstwach ViewModel, widokach SwiftUI i kontrolerach UIKit. Adnotacja może być zastosowana zarówno do poszczególnych metod, jak i do całego typu.

Adnotacja klasy lub struktury

Oznaczając klasę @MainActor, gwarantujesz, że wszystkie jej metody i właściwości są dostępne tylko na głównym wątku. Jest to szczególnie wygodne dla widoków SwiftUI i klas ObservableObject: wystarczy dodać @MainActor przed class, a wszystkie właściwości @Published są aktualizowane bezpiecznie.

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

Opakowywanie legacy-kodu

Podczas pracy ze starym kodem UIKit, gdzie przełączanie wątków było ręczne, można użyć MainActor.run do jawnego przełączenia. Jest to wygodne przy stopniowym przejściu na Swift Concurrency bez przepisywania całej bazy kodu.

swift
await MainActor.run {
    self.tableView.reloadData()
}

Ograniczenia @MainActor

Pomimo wszystkich zalet, @MainActor ma szereg ograniczeń, które należy uwzględnić przy projektowaniu architektury aplikacji. Zrozumienie granic zastosowania pomaga uniknąć nieprawidłowego użycia.

Wydajność przy intensywnym użyciu

Jeśli cały łańcuch wywołań jest oznaczony @MainActor, to każda ciężka praca będzie wykonywana na głównym wątku, powodując zawieszanie się UI. Zaleca się oznaczać @MainActor tylko warstwę UI, a logikę biznesową i żądania sieciowe pozostawić w aktorach tła lub globalnym wykonawcy.

Niekompatybilność z niektórymi API

Stare API oparte na callbackach (np. URLSession bez async/await) nie obsługują kontekstu aktora. Do integracji wymagane jest opakowanie z CheckedContinuation. Ponadto @MainActor nie jest kompatybilny z performSelector, target-action i innymi nieasynchronicznymi wzorcami UIKit.

Wielowątkowe debugowanie

Podczas debugowania aplikacji z @MainActor trudniej jest odtworzyć stany wyścigu, ponieważ kompilator zapobiega wielu z nich na etapie budowania, a nie w runtime. Może to jednak stworzyć fałszywe poczucie bezpieczeństwa: nieprawidłowa praca z współdzielonymi obiektami mutable (np. NSCache lub wspólne zmienne globalne) jest nadal możliwa, jeśli nie są oznaczone @MainActor i używane bez jawnej synchronizacji.

Testowanie @MainActor

@MainActor znacznie upraszcza testowanie logiki UI, ponieważ eliminuje potrzebę ręcznego przełączania wątków w testach. Istnieją jednak cechy, które należy uwzględnić przy pisaniu testów jednostkowych i UI.

Testy jednostkowe z MainActor

W XCTest środowisko wykonawcze automatycznie konfiguruje wykonawcę głównego wątku. Gdy metoda testowa jest uruchamiana na głównym wątku, wywołanie funkcji @MainActor nie wymaga dodatkowych ustawień — są one wykonywane w tym samym kontekście. Do testowania scenariuszy tła użyj MainActor.run wewnątrz Task z jawnym określeniem priorytetu i wykonawcy, osobno sprawdzając, że kod działa poprawnie przy wywołaniu z tła.

Jednym z popularnych podejść jest testowanie ViewModel z @MainActor, gdzie sprawdza się, że właściwości @Published są poprawnie aktualizowane po operacjach asynchronicznych. Dzięki dziedziczeniu kontekstu aktora, wywołanie await wewnątrz testu gwarantuje wykonanie na głównym wątku bez dodatkowych gwarancji DispatchQueue i ręcznego przełączania kontekstów, co upraszcza pisanie testów.

Sprawdzanie izolacji podczas refaktoringu

Podczas refaktoringu istniejącego kodu na Swift Concurrency sprawdzaj izolację @MainActor przez kompilator: wszelkie wywołania synchronicznych metod bez @MainActor z kontekstu @MainActor są oznaczone jako błąd. Ta właściwość jest używana do stopniowego przekształcania projektu na async/await: oznaczasz warstwę ViewModel jako @MainActor, a kompilator podświetla wszystkie niebezpieczne wywołania, które należy przenieść do aktorów tła.

Mockowanie i kontekst aktora

Podczas tworzenia mocków dla zależności @MainActor używaj protokołów z async-metodami, które deklarują funkcje asynchroniczne z typami zwracanymi. Pozwala to podmieniać serwisy sieciowe, bazy danych i inne zewnętrzne zależności bez naruszania izolacji aktora. Kompilator sprawdzi, że mock implementuje wszystkie wymagania dotyczące izolacji, zapobiegając przypadkowemu dostępowi do kodu @MainActor z testowych wątków tła.

Wait do oczekiwania na operacje asynchroniczne

Podczas synchronicznego testowania kodu @MainActor używaj XCTestExpectation do oczekiwania na zakończenie operacji asynchronicznych. Ustaw oczekiwanie w teście i wykonuj fulfillment wewnątrz domknięcia, które jest wykonywane na głównym wątku. Jeśli test zawiesza się w nieskończoność — najprawdopodobniej wywołanie na głównym wątku nie następuje i należy sprawdzić izolację aktora. Do debugowania kontekstu wykonania warto dodać sprawdzenie Thread.isMainThread wewnątrz kodu testowego.

Często zadawane pytania

Czy trzeba oznaczać całą klasę @MainActor?

Nie, wystarczy oznaczyć tylko metody, które aktualizują UI. Jednak jeśli w klasie jest kilka takich metod, łatwiej dodać @MainActor do całej klasy. Gwarantuje to, że wszystkie jej członki są wykonywane na głównym wątku i upraszcza utrzymanie kodu.

Czym @MainActor różni się od @globalActor?

@MainActor — to konkretna instancja globalnego aktora, powiązana z głównym wątkiem. @globalActor — to protokół do tworzenia własnych globalnych aktorów. Na przykład możesz utworzyć @BackgroundActor do wykonywania kodu na wątku tła, jeśli wymaga tego architektura projektu.

Czy można używać @MainActor bez async/await?

Tak, funkcje synchroniczne z @MainActor również są wykonywane na głównym wątku. Jednak główna wartość @MainActor ujawnia się właśnie z async/await, gdy funkcja asynchroniczna automatycznie wznawia się na głównym wątku bez ręcznego przełączania przez DispatchQueue.main.

Jak anulować zadanie @MainActor?

Task.cancel() działa z zadaniami @MainActor tak samo, jak ze zwykłymi. Zadanie @MainActor może sprawdzać Task.isCancelled lub rzucać CancellationError. Przy anulowaniu główny wątek nie jest blokowany — zadanie po prostu przestaje wykonywać się w najbliższym punkcie zawieszenia.

Co się stanie, jeśli wywołać @MainActor z wątku tła?

Kompilator gwarantuje bezpieczeństwo: jeśli wywołujesz funkcję @MainActor z kontekstu tła, kompilator wskaże błąd. W przypadku wywołań asynchronicznych wystarczy oznaczyć wywołujący kod await, a wykonawca sam przełączy się na główny wątek. Dla synchronicznych wymagane jest jawne przełączenie przez MainActor.run.

Podsumowanie

  • @MainActor — globalny aktor Swift gwarantujący wykonanie na głównym wątku.
  • Sprawdzenie przez kompilator eliminuje całą klasę błędów bezpieczeństwa UI.
  • Adnotacja całej klasy automatycznie umieszcza wszystkie jej metody na głównym wątku.
  • MainActor.run — jawne przełączenie dla legacy-kodu i synchronicznych kontekstów.
  • W przeciwieństwie do DispatchQueue.main, @MainActor nie tworzy domknięć i używa przełączenia wykonawcy.
  • Ciężkie obliczenia nie powinny być wykonywane pod @MainActor, aby uniknąć zawieszania się UI.
  • Dziedziczenie kontekstu aktora upraszcza łańcuchy wywołań asynchronicznych i czyni kod sekwencyjnym, przewidywalnym i bezpiecznym dla UI.

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.

Omów projekt

Przeczytaj również