SwiftUI - @MainActor
Theattribute in Swift is a global actor that ensures all code marked with it executes on the main thread. This is crucial for UI updates in SwiftUI and UIKit, which must happen on the main thread to r
The @MainActor attribute in Swift is a global actor that ensures all code marked with it executes on the main thread. This is crucial for UI updates in SwiftUI and UIKit, which must happen on the main thread to remain smooth and safe.
What is @MainActor and Why Use It?
@MainActor is a globally unique actor that serializes task execution on the main thread, preventing multiple threads from accessing the same state simultaneously and avoiding data races. Using @MainActor offers several advantages over the old method of "DispatchQueue.main.async":
- Compiler Enforcement: The Swift compiler enforces its rules, preventing you from accidentally calling @MainActor-isolated code from a background thread.
- Performance Optimization: The Swift concurrency model can sometimes skip the dispatch if it determines the code is already on the main thread, making your code more efficient.
- Integration with Modern Concurrency: It's designed to work natively with Swift's async/await syntax.
How to Use @MainActor?
You can apply @MainActor at different levels of granularity.
| Use Case | Code Example | Description |
| Mark an Entire Class | @MainActor class MyViewModel: ObservableObject { ... } | All properties/methods are main thread isolated. |
| Mark a Single Property | @MainActor var images: [UIImage] = [] | Property only accessible from the main thread. |
| Mark a Single Method | @MainActor func updateUI() { ... } | The guarantees method runs on the main thread. |
| Ad-hoc with MainActor.run | await MainActor.run { self.label.text = "Hi" } | Switches to the main thread for UI updates inside a task. |
Use nonisolated for Exceptions
If a class is marked with @MainActor but a specific method is thread-safe and doesn't touch the UI, mark it with nonisolated. This allows it to run on a background thread for better performance
@MainActor
class DataFetcher: ObservableObject {
@Published var data: [String] = [] // Isolated to MainActor
// This method can be called from any thread.
nonisolated func fetchFromServer() async -> [String] {
// ... background work
}
}
Important Behaviors and Common Pitfalls
- Actor Isolation is Inherited: If you mark a class as @MainActor, all its subclasses, methods, and properties will also be isolated to the main actor. A Task created within a @MainActor context will also inherit that isolation, which can block the UI if it performs heavy work.
- Initializers are Also Isolated: A @MainActor class's init() method must run on the main thread. This can cause errors if you try to initialize it from a non-isolated context.
- SwiftUI View body is already on @MainActor: The body property of a SwiftUI View is automatically annotated with @MainActor. If you use @StateObject or @ObservedObject in your view, the entire view becomes @MainActor. Explicitly annotating your ObservableObject view models with @MainActor is still recommended for clarity and to ensure all asynchronous work within the model updates properties on the correct thread.
Practical Example in SwiftUI
Here’s how you might use @MainActor in a typical SwiftUI view model.
// The entire ViewModel is isolated to the Main Actor.
@MainActor
class CounterViewModel: ObservableObject {
// Published properties will update on the main thread, safe for UI.
@Published var count = 0
@Published var isLoading = false
func increment() {
count += 1
}
func fetchData() async {
// This method is on the MainActor, so setting state is safe.
isLoading = true
// Perform heavy work in the background by using a detached task.
let result = await Task.detached(priority: .low) {
// This closure is not on the MainActor.
sleep(2) // Simulate heavy work.
return 42
}.value
// We are back on the MainActor. Update the state safely.
self.count = result
self.isLoading = false
}
}
In your SwiftUI view, you can use this view model without worrying about thread dispatches for state updates.
struct ContentView: View {
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment") {
viewModel.increment()
}
Button("Fetch Data") {
// Kick off an async task to call the ViewModel's method.
Task {
await viewModel.fetchData()
}
}
.disabled(viewModel.isLoading)
}
}
}
更多推荐


所有评论(0)