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

Your build is currently configured to use incompatible Java 21.0.3 and Gradle 8.2.1. Cannot sync the project.

Error in Android Migration Gradle 7.5 to 8.5 - java.lang.NullPointerException: Cannot invoke "String.length()" because "" is null

How to clean up the unused imports, variable and function from Typescript