Swift actors are fantastic for isolating instance data, providing a form of reference type that can be used in concurrent programs without introducing data races. Global actors are globally-unique actors.
The primary motivation for global actors is the main actor, and the semantics of this feature are tuned to the needs of main-thread execution. @MainActor is a global actor that describes the main thread, pretty handy given how often you need to make quick changes that update the user interface:
@globalActor
public actor MainActor {
public static let shared = MainActor(...)
}
For systems that use the Dispatch library as the underlying concurrency implementation, the main actor uses a custom executor that wraps the main dispatch queue.
The main thread is considered the special thread where all view-related access should be performed. If something will consume any significant time, e.g. calling a web service, compressing a file, etc., you will want to run code in a separate thread, and when the task completes, return to the main thread where you update the user interface.
The common pattern used with GCD of executing main-thread code via DispatchQueue.main.async
:
DispatchQueue.main.async {
// Safely update UI
}
If using Swift concurrency, you can synchronously use other @MainActor-annotated closures or MainActor.run()
method.
// (1)
Task.detached { @MainActor in
// Safely update UI
}
// (2)
Task { @MainActor [weak self] in
// Safely update UI
}
// (3)
Task {
await MainActor.run { [weak self] in
// Safely update UI
}
}
The magic of @MainActor is that it automatically forces methods or whole types to run on the main actor, a lot of the time without any further work from us. Instead of remembering switching back to main thread manually, now now the compiler does it for us implicitly.
@MainActor is a custom attribute, similar to property wrapper types or result builder types. Any declaration can state that it is main-actor-isolated by annotate with @MainActor, at which point all of the normal actor-isolation restrictions come into play: the declaration can only be synchronously accessed from another declaration on the main actor, but can be asynchronously accessed from elsewhere.
@MainActor var globalCounter = 0
@MainActor func incrementGlobalCounter() {
globalCounter += 1 // okay, we are on the main actor
}
func readCounter() async {
print(globalCounter) // error: cross-actor read requires 'await'
print(await globalCounter) // okay
}
By marking a declaration with @MainActor, you tell the compiler to enforce that other concurrency-aware code must call it on the main thread. There is a huge amount of existing of non-concurrency-aware code, though, and @MainActor does nothing for those cases yet, because doing so would cause far too many warnings even in cases that are actually safe.
If you’re calling @MainActor code inside a old-style completion handler, you still need to explicitly hop back to the main thread for now. Where possible, convert completion handlers to async functions or add @Sendable or @MainActor annotations to your completion handler callbacks.
Declarations that are not explicitly annotated with either @MainActor or nonisolated
can infer @MainActor isolation from several different places:
class FooViewController: UIViewController { // implicitly @MainActor
func baz() {
// implicitly @MainActor
}
}
class Foo {
@MainActor func baz() { ... }
}
class Bar: Foo {
override func baz() { ... } // implicitly @MainActor
}
protocol P {
@MainActor func f()
}
struct X { }
extension X: P {
func f() { } // implicitly @MainActor
}
struct Y: P { }
extension Y {
func f() { } // okay, not implicitly @MainActor because it's in a separate extension
// from the conformance to P
}
@MainActor protocol P {
func updateUI() { } // implicitly @MainActor
}
class C: P { } // C is implicitly @MainActor
// source file D.swift
class D { }
// different source file D-extensions.swift
extension D: P { // D is not implicitly @MainActor
func updateUI() { } // okay, implicitly @MainActor
}
@propertyWrapper
struct UIUpdating<Wrapped> {
@MainActor var wrappedValue: Wrapped
}
struct CounterView { // infers @MainActor from use of @UIUpdating
@UIUpdating var intValue: Int = 0
}