{"slug": "mainactor-in-swift-explained-with-code-examples", "title": "@MainActor in Swift explained with code examples", "summary": "Apple's Swift 6.2 introduces Default Actor Isolation in new Xcode 26 projects, automatically isolating app target code to the main actor, reducing the need for explicit @MainActor annotations. The @MainActor attribute, a global actor from the standard library, still functions identically, ensuring main-thread execution for properties, methods, instances, and closures, as explained by Antoine van der Lee in a SwiftLee article.", "body_md": "@MainActor is a global actor that performs its tasks on the main thread. You can use it to dispatch to the main thread by marking properties, methods, instances, or closures with the attribute. Instead of manually dispatching using `DispatchQueue.main.async`\n\n, you let the compiler enforce main thread execution for you.\n\nIf you’re new to Actors in Swift, I recommend reading my article [Actors in Swift: how to use and prevent data races](https://www.avanderlee.com/swift/actors/) first. The main actor behaves like other actors, with one important difference: it performs all its tasks on the main thread.\n\n[Let AI Control the iOS Simulator](https://www.rocketsim.app/blog/ai-agents-ios-simulator/?utm_source=swiftlee&utm_medium=owned_ad&utm_campaign=rocketsim_ai_simulator&utm_content=sponsor_block_v1)RocketSim lets AI coding agents inspect, navigate, and verify your running iOS Simulator app through a CLI and Agent Skill for Cursor, Claude, Codex, and Xcode. Give agents real UI feedback instead of letting them guess from code alone.\n\n[See how it works](https://www.rocketsim.app/blog/ai-agents-ios-simulator/?utm_source=swiftlee&utm_medium=owned_ad&utm_campaign=rocketsim_ai_simulator&utm_content=sponsor_block_v1_cta).\n\nWhat is a MainActor?\n\nA MainActor is a globally unique actor that performs its tasks on the main thread. You can use it for properties, methods, instances, and closures to perform tasks on the main thread. Proposal [SE-0316 Global Actors](https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md) introduced the main actor as an example of a global actor that inherits the `GlobalActor`\n\nprotocol.\n\nDo you still need @MainActor in Swift 6.2 and later?\n\nSince Swift 6.2, your code might already run on the main actor without a single annotation. New projects in Xcode 26 and later enable [Default Actor Isolation](https://www.avanderlee.com/concurrency/default-actor-isolation-in-swift-6-2/), which isolates all code inside your app target to the main actor unless you opt out. In other words: if you’re working inside a new project, you often don’t need to add @MainActor yourself anymore.\n\nThis change is part of a bigger set of improvements that I’ve covered in Approachable Concurrency in Swift 6.2: A Clear Guide. For this article, it’s important to know that the attribute still works exactly the same. Whether you write @MainActor yourself or the compiler infers it, the same rules apply.\n\nMainActor: a Global Actor from the standard library\n\nThe MainActor is a so-called **global actor**: an actor with a globally unique, shared instance. Its implementation is available by default and looks as follows:\n\n``` js\n@globalActor\nfinal actor MainActor: GlobalActor {\n    static let shared: MainActor\n}\n```\n\nAnywhere you use the @MainActor attribute, you’ll synchronize through this shared actor instance, ensuring mutually exclusive access to declarations. You can also define custom global actors, as I’ve explained in [Global actor in Swift Concurrency explained with code examples](https://www.avanderlee.com/concurrency/global-actor/).\n\nHow to use MainActor in Swift?\n\nYou can use a global actor with properties, methods, closures, and instances. For example, we could add the `@MainActor`\n\nattribute to a view model to perform all tasks on the main thread:\n\n```\n@MainActor\nfinal class HomeViewModel {\n    // ..\n}\n```\n\nUsing [nonisolated](https://www.avanderlee.com/swift/nonisolated-isolated/), we ensure methods without the main thread requirement perform as fast as possible by not waiting for the main thread to become available. You can only annotate a class with a global actor if it has no superclass, the superclass is annotated with the same global actor, or the superclass is NSObject. A subclass of a global-actor-annotated class must be isolated to the same global actor.\n\nIn other cases, we might want to define individual properties with a global actor:\n\n``` js\nfinal class HomeViewModel {\n    \n    @MainActor var images: [UIImage] = []\n\n}\n```\n\nMarking the `images`\n\nproperty with the `@MainActor`\n\nproperty ensures that it can only be updated from the main thread:\n\nThis is great when working with [MVVM in SwiftUI](https://www.avanderlee.com/swiftui/mvvm-architectural-coding-pattern-to-structure-views/) as you only want to trigger view redraws on the main thread.\n\nYou can mark individual methods with the attribute as well:\n\n```\n@MainActor func updateViews() {\n    /// Will always dispatch to the main thread as long as it's called\n    /// within a concurrency context.\n}\n```\n\nBe warned: this method is only guaranteed to run on the main thread when you call it from a concurrency context. The Swift 6 language mode catches most of these cases at compile time. I’ll dive deeper into this pitfall later in this article.\n\nAnd finally, you can mark closures to perform on the main thread:\n\n``` php\nfunc updateData(completion: @MainActor @escaping () -> ()) {\n    Task {\n        await someHeavyBackgroundOperation()\n        await completion()\n    }\n}\n```\n\nAlthough in this case, you should rewrite the `updateData`\n\nmethod to an async variant without needing a completion closure.\n\nFREE 5-Day Email Course: The Swift Concurrency Playbook\n\nA **FREE 5-day email course** revealing the **5 biggest mistakes** iOS developers make with with async/await that lead to App Store rejections And migration projects taking months instead of days (even if you've been writing Swift for years)\n\nUsing the main actor directly\n\nThe MainActor in Swift comes with an extension to use the actor directly:\n\n```\nextension MainActor {\n\n    /// Execute the given body closure on the main actor.\n    public static func run<T>(resultType: T.Type = T.self, body: @MainActor @Sendable () throws -> T) async rethrows -> T\n}\n```\n\nThis lets us call @MainActor in methods without using its attribute in the body.\n\n```\nTask {\n    await someHeavyBackgroundOperation()\n    await MainActor.run {\n        // Perform UI updates\n    }\n}\n```\n\nIn other words, there’s no need to use `DispatchQueue.main.async`\n\nanymore. However, I do recommend using the global attribute to restrict any access to the main thread. Without the global actor attribute, anyone could forget to use `MainActor.run`\n\n, potentially leading to UI updates taking place on a background thread.\n\nMainActor.run vs. Task { @MainActor in }\n\nAnother common way of dispatching to the main thread is by isolating the closure of a task:\n\n```\nTask { @MainActor in\n    imageView.image = image\n}\n```\n\nThe difference with `MainActor.run`\n\nis subtle but essential. A task with a main actor isolated closure performs all of its work on the main actor, while `MainActor.run`\n\nonly switches over for the given closure:\n\n``` js\nTask {\n    /// Runs somewhere in the background\n    let image = try await downloadImage()\n\n    /// Explicitly switch over to the main actor\n    await MainActor.run {\n        imageView.image = image\n    }\n}\n```\n\nI prefer to use `MainActor.run`\n\nwhen only part of my task requires execution on the main thread. If the whole task is about updating the UI, I isolate the closure using `Task { @MainActor in }`\n\ninstead.\n\nVerifying main actor execution with assumeIsolated\n\nIn rare cases, you might know for sure that your code runs on the main thread while the compiler can’t verify it. You can use `MainActor.assumeIsolated`\n\nto access main actor isolated code from a synchronous, nonisolated context:\n\n```\nnonisolated func handleCallback() {\n    MainActor.assumeIsolated {\n        updateViews()\n    }\n}\n```\n\nBe careful: `assumeIsolated`\n\ndoes not dispatch to the main thread for you. Instead, it stops program execution when it’s called from any other thread. In other words, only use it when you’re 100% sure your code already runs on the main thread.\n\nWhen should I add the @MainActor attribute?\n\nWhether you should add the attribute yourself depends on your project configuration. If your project uses Swift 6.2’s [default actor isolation](https://www.avanderlee.com/concurrency/default-actor-isolation-in-swift-6-2/), most of your code is already isolated to the main actor. The question becomes the opposite: when should you opt out? You can use [nonisolated](https://www.avanderlee.com/swift/nonisolated-isolated/) or [@concurrent](https://www.avanderlee.com/concurrency/concurrent-explained-with-code-examples/) for any code that shouldn’t block the main thread, like heavy processing tasks.\n\nIn projects without default isolation, I recommend adding @MainActor to any type or method that updates UI. Before Swift Concurrency, you might have written several dispatch statements to ensure callbacks arrive on the main thread. By rewriting your code to use async/await and the main actor, you allow dispatching to only take place if needed:\n\n``` php\n@MainActor\nfunc fetchImage(for url: URL) async throws -> UIImage {\n    let (data, _) = try await URLSession.shared.data(from: url)\n    guard let image = UIImage(data: data) else {\n        throw ImageFetchingError.imageDecodingFailed\n    }\n    return image\n}\n```\n\nThe @MainActor attribute ensures the logic executes on the main thread while the network request is still performed on a background thread. Dispatching to the main actor occurs only when needed, ensuring the best possible performance.\n\nCan you annotate an actor with @MainActor?\n\nNo, and the compiler will tell you an actor cannot have a global actor attribute. An actor defines its own isolation, which conflicts with isolating it to the global main actor. If you need a type that performs all of its work on the main thread, you should use a @MainActor annotated class instead.\n\n**Understanding Why **@MainActor** Doesn’t Always Ensure Main Thread Execution**\n\nIf you’re new to concurrency, you might think marking a method with @MainActor always ensures it runs on the main thread. Unfortunately, this is not guaranteed for synchronous methods in non-isolated contexts. Several teams found out the hard way, just like [Cyril ](https://x.com/cyrilcermak)[Cermak](https://www.linkedin.com/in/cyril-cermak-210a8b6b/), who reached out to me during one of my conference visits.\n\nBefore sharing the details, it’s good to know that Swift 6 language mode catches most of these occasions. However, since many teams are still [migrating to Swift 6](https://www.avanderlee.com/concurrency/swift-6-migrating-xcode-projects-packages/), I wanted to highlight this misunderstanding.\n\nSynchronous methods in non-isolated contexts run on the same thread as the caller, regardless of any actor annotations. Depending on which Swift language mode you’re using, you might or might not be warned about potential issues.\n\nFor example, this code compiles fine in Swift 5 language mode:\n\nThe dispatch via the global queue creates a non-isolated context and the @MainActor attribute isn’t respected.\n\nHowever, plenty of projects are still built with Swift 5, so it’s an essential pitfall to be aware of.\n\nIn larger projects, it’s likely that a chain of methods is called on a background thread. Eventually, such a chain can end up calling a method attributed to the main actor while synchronously dispatched to the same thread as the caller: a background thread. Therefore, see if you can get your code on Swift 6; otherwise, be sharp when using the main actor attribute.\n\nYou might not directly encounter issues if you always call the synchronous method from a source running on the main thread. However, awareness of this subtle difference is essential since your expectations might not align, resulting in unexpected UI-related crashes before migrating to Swift 6.\n\nContinuing your journey into Swift Concurrency\n\nThe concurrency changes are more than just actors and include other features that you can benefit from in your code. So, while at it, why not dive into other concurrency features?\n\n[Actors in Swift: how to use and prevent data races](https://www.avanderlee.com/swift/actors/)[Global actor in Swift Concurrency explained with code examples](https://www.avanderlee.com/concurrency/global-actor/)[Nonisolated and isolated keywords: Understanding Actor isolation](https://www.avanderlee.com/swift/nonisolated-isolated/)[Default Actor Isolation in Swift 6.2](https://www.avanderlee.com/concurrency/default-actor-isolation-in-swift-6-2/)[Threads vs. Tasks in Swift Concurrency](https://www.avanderlee.com/concurrency/threads-vs-tasks-in-swift-concurrency/)[Swift 6: What’s New and How to Migrate](https://www.avanderlee.com/concurrency/swift-6-migrating-xcode-projects-packages/)\n\n[Let AI Control the iOS Simulator](https://www.rocketsim.app/blog/ai-agents-ios-simulator/?utm_source=swiftlee&utm_medium=owned_ad&utm_campaign=rocketsim_ai_simulator&utm_content=sponsor_block_v1)RocketSim lets AI coding agents inspect, navigate, and verify your running iOS Simulator app through a CLI and Agent Skill for Cursor, Claude, Codex, and Xcode. Give agents real UI feedback instead of letting them guess from code alone.\n\n[See how it works](https://www.rocketsim.app/blog/ai-agents-ios-simulator/?utm_source=swiftlee&utm_medium=owned_ad&utm_campaign=rocketsim_ai_simulator&utm_content=sponsor_block_v1_cta).\n\nConclusion\n\nThe @MainActor attribute ensures your code performs on the main thread, which is essential for updating UI in your apps. You can apply it to properties, methods, instances, and closures, and since Swift 6.2, new projects even apply it by default. Methods like `MainActor.run`\n\nand `assumeIsolated`\n\ngive you direct control in cases where annotations don’t fit.\n\nIsolation is one of those concepts that only clicks once you’ve seen it applied across a real project. That’s exactly what I do in my [Swift Concurrency Course](https://www.swiftconcurrencycourse.com?utm_source=swiftlee&utm_medium=article&utm_campaign=mainactor-conclusion), where you’ll get confident with actors and isolation before starting your Swift 6 migration.\n\nIf you like to learn more tips on Swift, check out the [Swift category page](https://www.avanderlee.com/category/swift/). Feel free to contact me or [tweet me on Twitter](https://twitter.com/twannl) if you have any additional tips or feedback.\n\nThanks!", "url": "https://wpnews.pro/news/mainactor-in-swift-explained-with-code-examples", "canonical_source": "https://www.avanderlee.com/swift/mainactor-dispatch-main-thread/", "published_at": "2026-08-02 12:04:47+00:00", "updated_at": "2026-08-02 12:33:47.267183+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Apple", "Swift 6.2", "Xcode 26", "MainActor", "Antoine van der Lee", "SwiftLee"], "alternates": {"html": "https://wpnews.pro/news/mainactor-in-swift-explained-with-code-examples", "markdown": "https://wpnews.pro/news/mainactor-in-swift-explained-with-code-examples.md", "text": "https://wpnews.pro/news/mainactor-in-swift-explained-with-code-examples.txt", "jsonld": "https://wpnews.pro/news/mainactor-in-swift-explained-with-code-examples.jsonld"}}