DEV Community

Wesley de Groot
Wesley de Groot

Posted on • Originally published at wesleydegroot.nl on

What is @MainActor

In the ever-evolving world of Swift, concurrency has become a hot topic. With the introduction of Swift Concurrency, developers now have powerful tools at their disposal to write more efficient and responsive code. One such tool is the @MainActor attribute, which allows us to seamlessly execute code on the main queue.

What is @MainActor?

@MainActor is a global actor that utilizes the main queue for executing its work. In practical terms, this means that methods or types marked with @MainActor can safely modify the UI because they will always run on the main queue. Additionally, calling MainActor.run() allows us to push custom work to the main actor, ensuring it executes on the main queue.

Use Case: @MainActor

In the view model of a SwiftUI app, we often use @Published properties to update the UI. By marking the entire view model with @MainActor, we ensure that these UI updates always occur on the main queue. Here's an example:

@MainActor
class AccountViewModel: ObservableObject {
    @Published var username = "Anonymous"
    @Published var isAuthenticated = false
}
Enter fullscreen mode Exit fullscreen mode

The view model is marked with @MainActor, ensuring that any UI updates triggered by username or isAuthenticated always occur on the main queue. This is particularly useful when working with SwiftUI, where UI updates must always occur on the main queue.

struct AccountView: View {
    @StateObject var viewModel = AccountViewModel()

    var body: some View {
        VStack {
            Text("Welcome, \(viewModel.username)!")
                .font(.title)
                .padding()

            Text(viewModel.isAuthenticated ? "You are logged in." : ("You are not logged in.")
                .font(.headline)
                .padding()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, both username and isAuthenticated properties update the UI. By marking the entire class with @MainActor, we ensure that these UI updates always happen on the main actor. SwiftUI even takes care of this for us when we use @StateObject or @ObservedObject inside a view – the whole view runs on the main actor.

Why Explicitly Use @MainActor?

You might wonder: if SwiftUI already ensures main-actor behavior, why bother adding @MainActor explicitly? Here are a few reasons:

  1. Beyond SwiftUI : While SwiftUI handles main-actor behavior for observable objects, there are scenarios where our view models might be used outside of SwiftUI – perhaps in UIKit-based parts of our app or in more general Swift contexts. Explicitly marking them with @MainActor ensures consistent behavior across different contexts.

  2. Async/Await : If our view models perform their own asynchronous work (e.g., downloading data from a server using async/await), @MainActor remains beneficial. It guarantees that UI updates always occur on the main actor.

  3. Opting Out : If certain methods or computed properties should not run on the main actor, we can use nonisolated – similar to how we do with regular actors.

Global Actor Inference

Interestingly, any type containing properties of @MainActor objects also implicitly becomes @MainActor using global actor inference. Swift applies precise rules to ensure global-actor behavior works seamlessly without causing unnecessary hurdles.

In summary, @MainActor is a powerful tool for making UI updates safer and more predictable. Whether you're building SwiftUI views or working with UIKit, embracing @MainActor ensures your code dances gracefully on the main stage – the main queue.

Remember: actors are not suitable for observable objects; they must handle UI updates on the main actor. This is where @MainActor comes into play, ensuring that UI updates always occur on the main queue.

Caveats

While @MainActor is a powerful tool, it's important to use it judiciously. Overusing @MainActor can lead to performance issues, as it forces all work to run on the main queue. Always consider whether a given piece of code truly needs to run on the main queue before marking it with @MainActor.

Conclusion

Swift Concurrency has brought a new era of powerful tools to the Swift language. @MainActor is one such tool, allowing us to ensure that UI updates always occur on the main queue. By marking our types and methods with @MainActor, we can make our code safer and more predictable, ensuring that UI updates always occur on the main queue.

Resources:

https://developer.apple.com/documentation/swift/mainactor

Top comments (0)