The place View.course of will get its main-actor isolation from – Ole Begemann

The place View.course of will get its main-actor isolation from – Ole Begemann

[ad_1]

SwiftUI’s .course of modifier inherits its actor context from the encircling function. Contained in the occasion you determine .course of inside a view’s physique property, the async operation will run on the first actor on account of View.physique is (semi-secretly) annotated with @MainActor. Nonetheless, contained in the occasion you determine .course of from a helper property or function that isn’t @MainActor-annotated, the async operation will run contained within the cooperative thread pool.

Appropriate correct proper right here’s an occasion. Uncover the two .course of modifiers in physique and helperView. The code is analogous in every, nonetheless solely really thought-about one in all them compiles — in helperView, the choice to a main-actor-isolated function fails on account of we’re not on the first actor in that context:


The place View.course of will get its main-actor isolation from – Ole Begemann
We’re in a position to set up a main-actor-isolated function from inside physique, nonetheless not from a helper property.
import SwiftUI

@MainActor func onMainActor() {
  print("on MainActor")
}

struct ContentView: View {
  var physique: some View {
    VStack {
      helperView
      Textual content material materials supplies("in physique")
        .course of {
          // We're in a position to set up a @MainActor func with out await
          onMainActor()
        }
    }
  }

  var helperView: some View {
    Textual content material materials supplies("in helperView")
      .course of {
        // ❗️ Error: Expression is 'async' nonetheless is solely not marked with 'await'
        onMainActor()
      }
  }
}

This habits is attributable to 2 (semi-)hidden annotations contained within the SwiftUI framework:

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

  2. View.course of annotates its movement parameter with @_inheritActorContext, inflicting it to undertake the actor context from its use web site.

Sadly, none of these annotations are seen contained within the SwiftUI documentation, making it very obscure what’s occurring. The @MainActor annotation on View.physique is present in Xcode’s generated Swift interface for SwiftUI (Bounce to Definition of View), nonetheless that perform doesn’t work reliably for me, and as we’ll see, it doesn’t current all the truth, each.


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

To truly see the declarations the compiler sees, we’ve got to check out SwiftUI’s module interface file. A module interface is type of a header file for Swift modules. It lists the module’s public declarations and even the implementations of inlinable decisions. Module interfaces use frequent 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’s additionally varied .swiftinterface info in that itemizing, one per CPU constructing. Determine any really thought-about one in all them. Professional tip for viewing the file in Xcode: Editor > Syntax Coloring > Swift permits syntax highlighting.)

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

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

And likewise you’ll uncover this declaration for .course of, along with the @_inheritActorContext attribute:

@accessible(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 course of(
      priority: _Concurrency.TaskPriority = .userInitiated,
      @_inheritActorContext _ movement: @escaping @Sendable () async -> Swift.Void
    ) -> some SwiftUI.View {
      modifier(_TaskModifier(priority: priority, movement: movement))
    }
  #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 reveals the @_inheritActorContext annotatation on View.course of.

Armed with this information, each half makes further sense:

  • When used inside physique, course of inherits the @MainActor context from physique.
  • When used outside of physique, there isn’t a such factor as a such issue as a such subject as a implicit @MainActor annotation, so course of will run its operation on the cooperative thread pool by default. (Till the view incorporates an @ObservedObject or @StateObject property, which someway makes the whole view @MainActor. Nonetheless that’s a particular matter.)

The lesson: contained in the occasion you benefit from helper properties or decisions in your view, ponder annotating them with @MainActor to get the equal semantics as physique.

By the best approach by which, uncover that the actor context solely applies to code that is positioned immediately contained contained in the async closure, along with to synchronous decisions the closure calls. Async decisions choose their very non-public execution context, so any set up to an async function can change to a particular executor. For example, contained in the occasion you determine URLSession.info(from:) inside a main-actor-annotated function, the runtime will hop to the worldwide cooperative executor to execute that methodology. See SE-0338: Clarify the Execution of Non-Actor-Isolated Async Decisions for the precise pointers.

I understand Apple’s impetus to not current unofficial API or language choices contained within the documentation lest builders get the preposterous thought to make the most of these choices of their very non-public code!

Nonetheless it makes understanding so quite a bit more durable. Forward of I seen the annotations contained within the .swiftinterface file, the habits of the code at first of this textual content material materials definitely not made sense to me. Hiding the small print makes components appear like magic as shortly as they really aren’t. And that’s not good, each.

[ad_2]