jueves, febrero 22, 2024
InicioiOS DevelopmentThe place View.process will get its main-actor isolation from – Ole Begemann

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


SwiftUI’s .process modifier inherits its actor context from the encircling operate. If you happen to name .process inside a view’s physique property, the async operation will run on the principle actor as a result of View.physique is (semi-secretly) annotated with @MainActor. Nonetheless, if you happen to name .process from a helper property or operate that isn’t @MainActor-annotated, the async operation will run within the cooperative thread pool.

Right here’s an instance. Discover the 2 .process modifiers in physique and helperView. The code is equivalent in each, but solely one among them compiles — in helperView, the decision to a main-actor-isolated operate fails as a result of we’re not on the principle actor in that context:


Xcode showing the compiler diagnostic 'Expression is 'async' but is not marked with await'
We are able to name a main-actor-isolated operate from inside physique, however not from a helper property.
import SwiftUI

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

struct ContentView: View {
  var physique: some View {
    VStack {
      helperView
      Textual content("in physique")
        .process {
          // We are able to name a @MainActor func with out await
          onMainActor()
        }
    }
  }

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

This habits is brought on by two (semi-)hidden annotations within the SwiftUI framework:

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

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

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


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 capabilities. Module interfaces use regular Swift syntax and have the .swiftinterface file extension.

SwiftUI’s module interface is situated 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 will be a number of .swiftinterface recordsdata in that listing, one per CPU structure. Choose any one among them. Professional tip for viewing the file in Xcode: Editor > Syntax Coloring > Swift allows syntax highlighting.)

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

@obtainable(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 also you’ll discover this declaration for .process, together with the @_inheritActorContext attribute:

@obtainable(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 process(
      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 exhibits the @_inheritActorContext annotatation on View.process.

Armed with this data, all the pieces makes extra sense:

  • When used inside physique, process inherits the @MainActor context from physique.
  • When used outdoors of physique, there isn’t any implicit @MainActor annotation, so process will run its operation on the cooperative thread pool by default. (Except the view comprises an @ObservedObject or @StateObject property, which in some way makes the whole view @MainActor. However that’s a special matter.)

The lesson: if you happen to use helper properties or capabilities in your view, take into account annotating them with @MainActor to get the identical semantics as physique.

By the best way, notice that the actor context solely applies to code that’s positioned instantly contained in the async closure, in addition to to synchronous capabilities the closure calls. Async capabilities select their very own execution context, so any name to an async operate can change to a special executor. For instance, if you happen to name URLSession.information(from:) inside a main-actor-annotated operate, the runtime will hop to the worldwide cooperative executor to execute that technique. See SE-0338: Make clear the Execution of Non-Actor-Remoted Async Capabilities for the exact guidelines.

I perceive Apple’s impetus to not present unofficial API or language options within the documentation lest builders get the preposterous concept to make use of these options in their very own code!

But it surely makes understanding so a lot more durable. Earlier than I noticed the annotations within the .swiftinterface file, the habits of the code originally of this text by no means made sense to me. Hiding the main points makes issues look like magic after they truly aren’t. And that’s not good, both.

RELATED ARTICLES

DEJA UNA RESPUESTA

Por favor ingrese su comentario!
Por favor ingrese su nombre aquí

Most Popular

Recent Comments