Close Menu
geekfence.comgeekfence.com
    What's Hot

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Why Enterprise AI Scale Stalls

    December 28, 2025
    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook Instagram
    geekfence.comgeekfence.com
    • Home
    • UK Tech News
    • AI
    • Big Data
    • Cyber Security
      • Cloud Computing
      • iOS Development
    • IoT
    • Mobile
    • Software
      • Software Development
      • Software Engineering
    • Technology
      • Green Technology
      • Nanotechnology
    • Telecom
    geekfence.comgeekfence.com
    Home»iOS Development»Where View.task gets its main-actor isolation from – Ole Begemann
    iOS Development

    Where View.task gets its main-actor isolation from – Ole Begemann

    AdminBy AdminDecember 18, 2025No Comments5 Mins Read0 Views
    Facebook Twitter Pinterest LinkedIn Telegram Tumblr Email
    Where View.task gets its main-actor isolation from – Ole Begemann
    Share
    Facebook Twitter LinkedIn Pinterest Email


    SwiftUI’s .task modifier inherits its actor context from the surrounding function. If you call .task inside a view’s body property, the async operation will run on the main actor because View.body is (semi-secretly) annotated with @MainActor. However, if you call .task from a helper property or function that isn’t @MainActor-annotated, the async operation will run in the cooperative thread pool.

    Here’s an example. Notice the two .task modifiers in body and helperView. The code is identical in both, yet only one of them compiles — in helperView, the call to a main-actor-isolated function fails because we’re not on the main actor in that context:

    Where View.task gets its main-actor isolation from – Ole Begemann

    We can call a main-actor-isolated function from inside body, but not from a helper property.
    import SwiftUI
    
    @MainActor func onMainActor() {
      print("on MainActor")
    }
    
    struct ContentView: View {
      var body: some View {
        VStack {
          helperView
          Text("in body")
            .task {
              // We can call a @MainActor func without await
              onMainActor()
            }
        }
      }
    
      var helperView: some View {
        Text("in helperView")
          .task {
            // ❗️ Error: Expression is 'async' but is not marked with 'await'
            onMainActor()
          }
      }
    }
    

    This behavior is caused by two (semi-)hidden annotations in the SwiftUI framework:

    1. The View protocol annotates its body property with @MainActor. This transfers to all conforming types.

    2. View.task annotates its action parameter with @_inheritActorContext, causing it to adopt the actor context from its use site.

    Sadly, none of these annotations are visible in the SwiftUI documentation, making it very difficult to understand what’s going on. The @MainActor annotation on View.body is present in Xcode’s generated Swift interface for SwiftUI (Jump to Definition of View), but that feature doesn’t work reliably for me, and as we’ll see, it doesn’t show the whole truth, either.

    Xcode showing the generated interface for SwiftUI’s View protocol. The @MainActor annotation on View.body is selected.

    View.body is annotated with @MainActor in Xcode’s generated interface for SwiftUI.

    To really see the declarations the compiler sees, we need to look at SwiftUI’s module interface file. A module interface is like a header file for Swift modules. It lists the module’s public declarations and even the implementations of inlinable functions. Module interfaces use normal Swift syntax and have the .swiftinterface file extension.

    SwiftUI’s module interface is located at:

    [Path to Xcode.app]/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64e-apple-ios.swiftinterface
    


    (There can be multiple .swiftinterface files in that directory, one per CPU architecture. Pick any one of them. Pro tip for viewing the file in Xcode: Editor > Syntax Coloring > Swift enables syntax highlighting.)

    Inside, you’ll find that View.body has the @MainActor(unsafe) attribute:

    @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
    @_typeEraser(AnyView) public protocol View {
      // …
      @SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe) var body: Self.Body { get }
    }
    

    And you’ll find this declaration for .task, including the @_inheritActorContext attribute:

    @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
    extension SwiftUI.View {
      #if compiler(>=5.3) && $AsyncAwait && $Sendable && $InheritActorContext
        @inlinable public func task(
          priority: _Concurrency.TaskPriority = .userInitiated,
          @_inheritActorContext _ action: @escaping @Sendable () async -> Swift.Void
        ) -> some SwiftUI.View {
          modifier(_TaskModifier(priority: priority, action: action))
        }
      #endif
      // …
    }
    

    Xcode showing the declaration for the View.task method in the SwiftUI.swiftinterface file. The @_inheritActorContext annotation is selected.

    SwiftUI’s module interface file shows the @_inheritActorContext annotatation on View.task.

    Armed with this knowledge, everything makes more sense:

    • When used inside body, task inherits the @MainActor context from body.
    • When used outside of body, there is no implicit @MainActor annotation, so task will run its operation on the cooperative thread pool by default.
    • Unless the view contains an @ObservedObject or @StateObject property, which makes the entire view @MainActor via this obscure rule for property wrappers whose wrappedValue property is bound to a global actor:

      A struct or class containing a wrapped instance property with a global actor-qualified wrappedValue infers actor isolation from that property wrapper

      Update May 1, 2024: SE-0401: Remove Actor Isolation Inference caused by Property Wrappers removes the above rule when compiling in Swift 6 language mode. This is a good change because it makes reasoning about actor isolation simpler. In the Swift 5 language mode, you can opt into the better behavior with the -enable-upcoming-feature DisableOutwardActorInference compiler flags. I recommend you do.

    The lesson: if you use helper properties or functions in your view, consider annotating them with @MainActor to get the same semantics as body.

    By the way, note that the actor context only applies to code that is placed directly inside the async closure, as well as to synchronous functions the closure calls. Async functions choose their own execution context, so any call to an async function can switch to a different executor. For example, if you call URLSession.data(from:) inside a main-actor-annotated function, the runtime will hop to the global cooperative executor to execute that method. See SE-0338: Clarify the Execution of Non-Actor-Isolated Async Functions for the precise rules.

    I understand Apple’s impetus not to show unofficial API or language features in the documentation lest developers get the preposterous idea to use these features in their own code!

    But it makes understanding so much harder. Before I saw the annotations in the .swiftinterface file, the behavior of the code at the beginning of this article never made sense to me. Hiding the details makes things seem like magic when they actually aren’t. And that’s not good, either.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email

    Related Posts

    SwiftText | Cocoanetics

    December 28, 2025

    Uniquely identifying views – The.Swift.Dev.

    December 27, 2025

    Unable to upload my app with Transporter. Some kind of version mismatch? [duplicate]

    December 26, 2025

    Experimenting with Live Activities – Ole Begemann

    December 25, 2025

    Announcing Mastering SwiftUI for iOS 18 and Xcode 16

    December 24, 2025

    Grouping Liquid Glass components using glassEffectUnion on iOS 26 – Donny Wals

    December 22, 2025
    Top Posts

    Understanding U-Net Architecture in Deep Learning

    November 25, 20258 Views

    Microsoft 365 Copilot now enables you to build apps and workflows

    October 29, 20258 Views

    Here’s the latest company planning for gene-edited babies

    November 2, 20257 Views
    Don't Miss

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    After laying out our bold CXM predictions for 2025 and then assessing how those bets played out…

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Why Enterprise AI Scale Stalls

    December 28, 2025

    New serverless customization in Amazon SageMaker AI accelerates model fine-tuning

    December 28, 2025
    Stay In Touch
    • Facebook
    • Instagram
    About Us

    At GeekFence, we are a team of tech-enthusiasts, industry watchers and content creators who believe that technology isn’t just about gadgets—it’s about how innovation transforms our lives, work and society. We’ve come together to build a place where readers, thinkers and industry insiders can converge to explore what’s next in tech.

    Our Picks

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Subscribe to Updates

    Please enable JavaScript in your browser to complete this form.
    Loading
    • About Us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • Terms and Conditions
    © 2025 Geekfence.All Rigt Reserved.

    Type above and press Enter to search. Press Esc to cancel.