MainActor: Keep Your UI on the Main Thread

The MainActor is a global actor ensuring code runs on the main thread, the only safe place for UI updates. Use it when modifying UIKit or SwiftUI views. The footgun is over-applying it to a whole class, which can serialize background work and freeze the.
WHY IT EXISTS: UI frameworks like UIKit and SwiftUI are not thread-safe. Modifying UI from a background thread causes crashes, visual glitches, and unpredictable behavior. Historically, developers manually used DispatchQueue.main.async to fix this, but it was error-prone and only checked at runtime.
THE MENTAL MODEL: Think of @MainActor as a dedicated, compiler-enforced lane for all UI work. It ensures any function or property marked with it can only be accessed from the main thread. This prevents the "collisions" (race conditions) of multiple threads trying to change the UI at once, turning potential runtime crashes into build-time errors.
HOW IT WORKS: @MainActor is a global actor provided by the Swift Concurrency system. When you apply the @MainActor attribute to a function, property, or type, the compiler enforces that it's only accessed from the main thread. If you call a main-actor-isolated function from a background task, Swift automatically suspends your task and reschedules it to run on the main thread's executor. You can also explicitly hop to the main thread with await MainActor.run { ... }.
WHEN TO USE IT: Apply @MainActor to any code that directly reads from or writes to the UI. This includes updating a UILabel, modifying a UIView's properties, presenting a view controller, or updating @Published properties in an ObservableObject that drive a SwiftUI view. It's essential for the final step of any asynchronous operation that results in a UI change.
WHEN NOT TO USE IT: Do not use @MainActor for long-running, blocking, or CPU-intensive work. This includes network requests, parsing large JSON files, or performing complex calculations. Placing these tasks on the MainActor will block the main thread, making your app unresponsive and feel frozen to the user. Perform heavy lifting on a background thread or a different actor.
ONE CANONICAL EXAMPLE: Consider a view model fetching user data. The network request function, fetchUser(), should run in the background. Once the user data is received, you need to update a @Published property, say userName, on your view model. This property update must happen on the main thread. By marking just the property with @MainActor (e.g., @MainActor @Published var userName: String = ""), you ensure that any assignment to userName is safely dispatched to the main thread, without forcing the entire fetchUser function to run there.
Read the original → developer.apple.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.