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
}
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()
}
}
}
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:
-
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. -
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. -
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
Read more
- Simplifying Game Controller Integration with GameControllerKit • 5 minutes reading time.
- @ViewBuilder in Swift • 4 minutes reading time.
- Safely unwrap optional values in SwiftUI bindings • 4 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook