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 encompassing operate. Throughout the event you establish .course of inside a view’s physique property, the async operation will run on the primary actor on account of View.physique is (semi-secretly) annotated with @MainActor. Nonetheless, inside the event you establish .course of from a helper property or operate that isn’t @MainActor-annotated, the async operation will run contained in the cooperative thread pool.

Correct proper right here’s an event. Uncover the 2 .course of modifiers in physique and helperView. The code is identical in each, nonetheless solely truly thought-about considered one of them compiles — in helperView, the selection to a main-actor-isolated operate fails on account of we’re not on the primary actor in that context:


The place View.course of will get its main-actor isolation from – Ole Begemann
We’re ready to establish a main-actor-isolated operate 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("in physique")
        .course of {
          // We're ready to establish a @MainActor func with out await
          onMainActor()
        }
    }
  }

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

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

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

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

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


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 essentially see the declarations the compiler sees, we have to take a look at SwiftUI’s module interface file. A module interface is sort of a header file for Swift modules. It lists the module’s public declarations and even the implementations of inlinable choices. Module interfaces use widespread Swift syntax and have the .swiftinterface file extension.

SwiftUI’s module interface is positioned 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 is also numerous .swiftinterface knowledge in that itemizing, one per CPU building. Decide any truly thought-about considered one of them. Expert 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, together 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(
      precedence: _Concurrency.TaskPriority = .userInitiated,
      @_inheritActorContext _ motion: @escaping @Sendable () async -> Swift.Void
    ) -> some SwiftUI.View {
      modifier(_TaskModifier(precedence: precedence, motion: motion))
    }
  #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 data, every half makes additional sense:

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

The lesson: inside the event you make the most of helper properties or choices in your view, ponder annotating them with @MainActor to get the equal semantics as physique.

By the easiest way by which, uncover that the actor context solely applies to code that’s positioned instantly contained inside the async closure, together with to synchronous choices the closure calls. Async choices select their very private execution context, so any establish to an async operate can change to a selected executor. For instance, inside the event you establish URLSession.knowledge(from:) inside a main-actor-annotated operate, the runtime will hop to the worldwide cooperative executor to execute that methodology. See SE-0338: Make clear the Execution of Non-Actor-Remoted Async Choices for the exact pointers.

I perceive Apple’s impetus to not present unofficial API or language selections contained in the documentation lest builders get the preposterous thought to utilize these selections of their very private code!

Nonetheless it makes understanding so a lot tougher. Before I noticed the annotations contained in the .swiftinterface file, the habits of the code at first of this textual content material by no means made sense to me. Hiding the main points makes factors appear to be magic as quickly as they actually aren’t. And that’s not good, every.

[ad_2]