@MainActor — co to je, aplikace a vlastnosti v asynchronním kódu Swift

Autor: IT Sectr Publikováno: 2026-03-19 Doba čtení: 8 min

@MainActor je globální actor v jazyce Swift, který garantuje provedení kódu na hlavním vlákně. Podle Apple Developer, 2024, @MainActor automatizuje přepínání na hlavní vlákno při práci s UI, čímž zbavuje vývojáře ručního volání DispatchQueue.main.async. Anotace se objevila ve Swift 5.5 spolu se systémem async/await.

Hlavní body

  • @MainActor — globální actor Swift pro záruku provedení na hlavním vlákně.
  • Systém async/await — základ, na kterém je @MainActor postaven.
  • Anotace třídy automaticky umísťuje všechny její metody na hlavní vlákno.
  • Na rozdíl od DispatchQueue.main, @MainActor kontroluje vlákno na úrovni překladače.
  • Aktualizace UI — hlavní oblast použití @MainActor ve vývoji iOS.

Co je @MainActor?

@MainActor je globální actor (global actor) ve Swift, který kombinuje vlastnosti actorů se zárukou provedení na hlavním vlákně aplikace. Je součástí systému konkurence Swift, představeného ve Swift 5.5 spolu s async/await a strukturovanou konkurencí. Anotace umožňuje vývojáři nemyslet na ruční přepínání vláken a snižuje počet chyb UI.

Definice a místo ve Swift Concurrency

Actor ve Swift je referenční typ, který izoluje svůj stav a garantuje, že pouze jedno vlákno jej může měnit. @MainActor je speciální globální actor, jehož vykonavatelem je hlavní vlákno. Jakýkoli kód označený @MainActor se provádí na hlavním vlákně — i když byl zavolán z úkolu na pozadí.

Před příchodem @MainActor vývojáři ručně přepínali na hlavní vlákno pomocí DispatchQueue.main.async. To byl zdroj častých chyb: vývojáři zapomínali přepnout, což vedlo k pádům kvůli aktualizaci UI na nehlavním vlákně. @MainActor řeší tento problém na úrovni typového systému.

Důvody vzniku

Zdrojem většiny chyb v iOS aplikacích je nebezpečí UI — aktualizace rozhraní z vlákna na pozadí. Apple zabudoval @MainActor do Swift Concurrency, aby přepínání na hlavní vlákno bylo automatické a kontrolovatelné překladačem, čímž eliminoval celou třídu runtime chyb.

Jak @MainActor funguje?

Princip fungování @MainActor je založen na systému provádění Swift Concurrency. Když vlákno zavolá funkci označenou @MainActor, plánovač ji pozastaví na aktuálním vykonavateli a obnoví na hlavním vlákně. Překladač sleduje hranice volání a garantuje bezpečnost.

Vykonavatel hlavního vlákna

Za provedení @MainActor odpovídá MainActor.shared — vykonavatel spojený s hlavním vláknem aplikace. Když je asynchronní funkce označena @MainActor, vždy se obnoví na tomto vykonavateli, bez ohledu na to, na kterém vlákně byl původní úkol spuštěn.

swift
import SwiftUI

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

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // bezpečně, MainActor garantuje hlavní vlákno
    }
}

Dědičnost kontextu actoru

Pokud je funkce označena @MainActor a volá jinou asynchronní funkci, standardně dědí kontext actoru. To znamená, že všechna vnořená volání se také provádějí na hlavním vlákně, pokud není stanoveno jinak. Překladač to sleduje a při pokusu o předání nekompatibilního uzávěru vydá chybu.

@MainActor vs DispatchQueue.main

Srovnání @MainActor a DispatchQueue.main pomáhá pochopit, proč je nový mechanismus považován za bezpečnější a pohodlnější, i když oba řeší stejný úkol — provedení kódu na hlavním vlákně.

Bezpečnost na úrovni typů

@MainActor je kontrola na úrovni překladače. Pokud se pokusíte zavolat funkci @MainActor z nebezpečného kontextu, překladač vydá varování nebo chybu. DispatchQueue.main.async je runtime volání: kód se přeloží, ale může spadnout za běhu při pokusu o aktualizaci UI z vlákna na pozadí.

Výkon a redundance

DispatchQueue.main.async přidává do fronty blok, který může být proveden se zpožděním. @MainActor s async/await provádí přímé přepnutí vykonavatele bez vytváření nadbytečných uzávěrů. To snižuje režii a činí kód předvídatelnějším z hlediska doby provádění.

swift
// Starý přístup
DispatchQueue.main.async {
    self.updateUI()
}

// Nový přístup s @MainActor
@MainActor
func updateUI() {
    // provádí se na hlavním vlákně
    self.label.text = "Aktualizováno"
}
Kritérium@MainActorDispatchQueue.main
Kontrolapřekladačruntime
Syntaxeanotace (deklarativní)volání (imperativní)
Režienízká (přepnutí vykonavatele)střední (uzávěr + fronta)
Testovatelnostvysoká (MainActor.shared lze nahradit)nízká (obtížně mockovatelné)

Použití @MainActor v iOS projektech

V reálných iOS projektech se @MainActor aplikuje ve vrstvách ViewModel, SwiftUI zobrazeních a UIKit kontrolerech. Anotaci lze aplikovat jak na jednotlivé metody, tak na celý typ.

Anotace třídy nebo struktury

Označením třídy @MainActor garantujete, že všechny její metody a vlastnosti jsou přístupné pouze na hlavním vlákně. To je zvláště výhodné pro SwiftUI zobrazení a ObservableObject třídy: jednoduše přidáte @MainActor před class a všechny @Published vlastnosti se bezpečně aktualizují.

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
    }
}

Zabalování legacy kódu

Při práci se starým UIKit kódem, kde bylo přepínání vláken ruční, lze použít MainActor.run pro explicitní přepnutí. To je výhodné pro postupný přechod na Swift Concurrency bez přepisování celé kódové základny.

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

Omezení @MainActor

Přes všechny výhody má @MainActor řadu omezení, která je třeba zvážit při navrhování architektury aplikace. Pochopení hranic použití pomáhá předejít nesprávnému použití.

Výkon při intenzivním použití

Pokud je celý řetězec volání označen @MainActor, jakákoli těžká práce se bude provádět na hlavním vlákně, což způsobí zaseknutí UI. Doporučuje se označit @MainActor pouze UI vrstvu a obchodní logiku a síťové požadavky ponechat v actorích na pozadí nebo globálním vykonavateli.

Nekompatibilita s některými API

Stará API založená na callback (např. URLSession bez async/await) nepodporují kontext actoru. Pro integraci je vyžadováno zabalení s CheckedContinuation. Také @MainActor není kompatibilní s performSelector, target-action a jinými neasynchronními vzory UIKit.

Multi-thread ladění

Při ladění aplikací s @MainActor je obtížnější reprodukovat závodné stavy, protože překladač jim mnohým předchází ve fázi sestavování, nikoli za běhu. To však může vytvářet falešný pocit bezpečí: nesprávná práce se sdílenými mutable objekty (např. NSCache nebo sdílené globální proměnné) je stále možná, pokud nejsou označeny @MainActor a používány bez explicitní synchronizace.

Testování @MainActor

@MainActor výrazně zjednodušuje testování UI logiky, protože odstraňuje potřebu ručního přepínání vláken v testech. Existují však vlastnosti, které je třeba zvážit při psaní jednotkových testů a UI testů.

Jednotkové testy s MainActor

V XCTest prostředí provádění automaticky konfiguruje vykonavatele hlavního vlákna. Když se testovací metoda provádí na hlavním vlákně, volání funkcí @MainActor nevyžaduje další nastavení — provádějí se ve stejném kontextu. Pro testování scénářů na pozadí použijte MainActor.run uvnitř Task s explicitním určením priority a vykonavatele, separátně ověřte, že kód funguje správně při volání z pozadí.

Jedním z běžných přístupů je testování ViewModel s @MainActor, kde se ověřuje, že @Published vlastnosti se správně aktualizují po asynchronních operacích. Díky dědičnosti kontextu actoru garantuje volání await uvnitř testu provedení na hlavním vlákně bez dalších DispatchQueue záruk a ručního přepínání kontextů, což zjednodušuje psaní testů.

Kontrola izolace při refaktorování

Při refaktorování existujícího kódu na Swift Concurrency kontrolujte izolaci @MainActor pomocí překladače: jakákoli volání synchronních metod bez @MainActor z kontextu @MainActor jsou označena jako chyba. Tato vlastnost se používá pro postupný přechod projektu na async/await: označíte vrstvu ViewModel jako @MainActor a překladač zvýrazní všechna nebezpečná volání, která je třeba přesunout do actorů na pozadí.

Mockování a kontext actoru

Při vytváření mocků pro závislosti @MainActor použijte protokoly s async metodami, které deklarují asynchronní funkce s návratovými typy. To umožňuje nahrazovat síťové služby, databáze a další externí závislosti bez porušení izolace actoru. Překladač ověří, zda mock splňuje všechny požadavky izolace, čímž brání náhodnému přístupu k @MainActor kódu z testovacích vláken na pozadí.

Čekání na asynchronní operace

Při synchronním testování @MainActor kódu použijte XCTestExpectation pro čekání na dokončení asynchronních operací. Nastavte očekávání v testu a proveďte fulfillment uvnitř uzávěru, který se provádí na hlavním vlákně. Pokud test nekonečně visí — volání na hlavním vlákně se pravděpodobně neprovádí a je třeba zkontrolovat izolaci actoru. Pro ladění kontextu provádění je užitečné přidat kontrolu Thread.isMainThread uvnitř testovacího kódu.

Často kladené otázky

Je povinné označit celou třídu @MainActor?

Ne, stačí označit pouze metody, které aktualizují UI. Pokud je však ve třídě několik takových metod, je jednodušší přidat @MainActor k celé třídě. To garantuje, že všechny její členy se provádějí na hlavním vlákně a zjednodušuje údržbu kódu.

Čím se @MainActor liší od @globalActor?

@MainActor je konkrétní instance globálního actoru, připoutaná k hlavnímu vláknu. @globalActor je protokol pro vytváření vlastních globálních actorů. Například můžete vytvořit @BackgroundActor pro provádění kódu na vlákně na pozadí, pokud to architektura projektu vyžaduje.

Lze @MainActor použít bez async/await?

Ano, synchronní funkce s @MainActor se také provádějí na hlavním vlákně. Hlavní hodnota @MainActor se však odhaluje právě s async/await, když se asynchronní funkce automaticky obnoví na hlavním vlákně bez ručního přepnutí přes DispatchQueue.main.

Jak zrušit úkol @MainActor?

Task.cancel() pracuje s úkoly @MainActor stejně jako s běžnými úkoly. Úkol @MainActor může kontrolovat Task.isCancelled nebo vyhodit CancellationError. Při zrušení není hlavní vlákno blokováno — úkol jednoduše zastaví provádění v nejbližším bodě pozastavení.

Co se stane, když je @MainActor zavolán z vlákna na pozadí?

Překladač garantuje bezpečnost: pokud zavoláte funkci @MainActor z kontextu na pozadí, překladač označí chybu. Pro asynchronní volání stačí označit volající kód await a vykonavatel sám přepne na hlavní vlákno. Pro synchronní volání je vyžadováno explicitní přepnutí pomocí MainActor.run.

Shrnutí

  • @MainActor — globální actor Swift garantující provedení na hlavním vlákně.
  • Kontrola překladačem eliminuje celou třídu chyb bezpečnosti UI.
  • Anotace celé třídy automaticky umísťuje všechny její metody na hlavní vlákno.
  • MainActor.run — explicitní přepnutí pro legacy kód a synchronní kontexty.
  • Na rozdíl od DispatchQueue.main, @MainActor nevytváří uzávěry a používá přepnutí vykonavatele.
  • Těžké výpočty by se neměly provádět pod @MainActor, aby se zabránilo zaseknutí UI.
  • Dědičnost kontextu actoru zjednodušuje řetězce asynchronních volání a činí kód sekvenčním, předvídatelným a bezpečným pro UI.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také