SwiftUI - Tutorial 2 - 🧵 Understanding @MainActor in SwiftUI — A Beginner’s Guide

 

🧵 Understanding @MainActor in SwiftUI — A Beginner’s Guide

When you start building iOS apps with SwiftUI, sooner or later you’ll run into this warning:

⚠️ “Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.”

Sounds scary? Don’t worry 🙂 This is exactly where @MainActor comes to the rescue.


🔹 What is @MainActor?

In Swift, an actor is like a traffic controller that makes sure only one thing accesses a resource at a time.

The @MainActor is a special actor provided by Swift that guarantees the code it marks will always run on the main thread.

And that’s important because in iOS development:

  • UI updates must always happen on the main thread.


🔹 Why do we need @MainActor in SwiftUI?

SwiftUI automatically refreshes the UI when your @Published properties change. But if those changes happen on a background thread (for example, after fetching data from the network), your app might:

  • Show warnings

  • Update incorrectly

  • Or even crash 🚨

By using @MainActor, you make sure that any UI-related changes happen on the main thread, safely.


🔹 How to Use @MainActor

✅ 1. On a Whole Class

Mark the entire ViewModel with @MainActor. This ensures all properties and methods run on the main thread.

@MainActor class CounterViewModel: ObservableObject { @Published var count = 0 func increment() { count += 1 // always safe for UI } }

✅ 2. On a Single Function

If you don’t want to mark the whole class, you can apply it to specific methods.

class NetworkManager { @MainActor func updateUI() { print("This will always run on the main thread") } }

✅ 3. Inside Async/Await

When fetching data in the background, you can jump back to the main thread using MainActor.run {}.

class UserViewModel: ObservableObject { @Published var username: String = "" func loadUser() async { let name = await fetchFromAPI() // background thread await MainActor.run { // back to main thread self.username = name } } }

🔹 Real-World Problem Example

Without @MainActor:

class MyViewModel: ObservableObject { @Published var text = "" func fetchData() { DispatchQueue.global().async { self.text = "Hello from background!" // ❌ Warning! } } }

With @MainActor:

@MainActor class MyViewModel: ObservableObject { @Published var text = "" func fetchData() { DispatchQueue.global().async { Task { @MainActor in self.text = "Hello safely!" } } } }

No more warnings 🎉


🔹 Beginner-Friendly Summary

  • @MainActor ensures code runs on the main thread.

  • In SwiftUI, this is critical because UI updates must only happen on the main thread.

  • You can use it on:

    • Entire classes (most common for ViewModels)

    • Functions

    • Or blocks of code using MainActor.run {}

👉 When in doubt, if your code touches UI state (@Published, @State, ViewModel), it probably needs @MainActor.


✨ With this knowledge, next time you see that scary warning, you’ll know exactly how to fix it.

Comments

Popular posts from this blog

SonarQube With Angular 19 on Windows: A Complete Setup and Integration Guide

Setting Up Jenkins for Flutter App on macOS