Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

The @MainActor attribute is a global actor annotation in Swift’s structured concurrency model that statically guarantees a declaration executes exclusively on the main thread. By applying this attribute, the Swift compiler enforces thread-safety at compile time, ensuring that state mutations and function invocations associated with the annotated entity are serialized through the main dispatch queue. Under the hood, @MainActor is a singleton actor conforming to the GlobalActor protocol. It utilizes a custom executor that routes all of its isolated tasks to DispatchQueue.main.

Syntax and Application

The attribute can be applied to various declarations, including classes, structs, protocols, functions, properties, and closures. When applied to a type, all of its properties and methods implicitly inherit the main actor isolation.
// Type-level isolation
@MainActor
final class StateContainer {
    var counter: Int = 0
    
    func increment() {
        counter += 1
    }
}

// Function-level isolation
@MainActor
func performMainThreadOperation() {}

// Property-level isolation
class DataProcessor {
    @MainActor var status: String = "Idle"
}

Isolation Domains and Cross-Actor Access

When a declaration is annotated with @MainActor, it enters the main actor’s isolation domain. The compiler enforces strict rules regarding how this domain is accessed:
  1. Synchronous Access: Code already executing within the @MainActor isolation domain can access other @MainActor declarations synchronously.
  2. Asynchronous Access: Code executing outside the @MainActor isolation domain (e.g., from a background task or a different actor) must cross the isolation boundary asynchronously using the await keyword.
func backgroundTask() async {
    let container = await StateContainer()
    
    // Cross-actor mutation requires 'await'
    await container.increment()
}

Opting Out with nonisolated

When an entire type is annotated with @MainActor, you can selectively exclude specific members from the isolation domain using the nonisolated keyword. This allows those specific members to be accessed synchronously from any thread. The nonisolated keyword cannot be applied to any stored properties (var or let). It is restricted to methods and computed properties. While immutable stored properties (let) that are Sendable and initialized inline are implicitly accessible from outside the isolation domain, explicitly annotating them with nonisolated is invalid Swift syntax and will produce a compiler error.
@MainActor
class ConfigurationManager {
    var configName: String = "Default"
    let id: UUID = UUID() // Implicitly accessible anywhere because UUID is Sendable
    
    // Opts out of MainActor isolation
    nonisolated var defaultHash: Int {
        return 0
    }

    // Opts out of MainActor isolation
    nonisolated func computeHash(for value: String) -> Int {
        return value.hashValue
    }
}

Closure Isolation

Closures can be explicitly bound to the main actor. This is frequently used when spawning unstructured tasks that require main thread execution.
Task { @MainActor in
    // Closure body is isolated to the main actor
    performMainThreadOperation()
}

Protocol Conformance and Inheritance

If a protocol is annotated with @MainActor, a type that conforms to that protocol infers @MainActor isolation only if the conformance is declared on the primary type declaration. If the conformance is added via an extension, only the members declared within that specific extension infer the isolation; the rest of the type remains unisolated.
@MainActor
protocol UIUpdatable {
    func updateUI()
}

// Entire type infers @MainActor isolation
struct PrimaryComponent: UIUpdatable {
    func updateUI() {}
}

struct SecondaryComponent {}

// Only members within this extension infer @MainActor isolation
extension SecondaryComponent: UIUpdatable {
    func updateUI() {}
}
Similarly, if a superclass is annotated with @MainActor, all of its subclasses inherit the attribute. Subclasses cannot remove the @MainActor isolation inherited from a superclass.

Explicit Execution and Assertions

The MainActor type provides specific APIs for executing code and asserting isolation dynamically when the compiler cannot statically verify the context. MainActor.run This method executes a block of code on the main actor from an async context without spawning a new unstructured task. It is used to transition execution to the main thread and group multiple isolated operations.
func processData() async {
    let result = await performHeavyComputation()
    
    // Transitions to the main actor without a new Task
    await MainActor.run {
        updateState(with: result)
        performMainThreadOperation()
    }
}
MainActor.assumeIsolated This method synchronously asserts that the current execution context is the main actor. It is used when bridging with older callback-based APIs or delegates where the compiler cannot statically prove the execution context, but the runtime environment guarantees it. The closure receives an isolated reference to the MainActor as its argument, which must be explicitly handled or ignored (e.g., using _ in). If the code is executed off the main actor, this method triggers a fatal runtime crash.
func legacyCallbackHandler() {
    // The compiler cannot statically verify this is on the main thread.
    // assumeIsolated asserts the context synchronously.
    MainActor.assumeIsolated { _ in
        performMainThreadOperation()
    }
}
Master Swift with Deep Grasping Methodology!Learn More