[ad_1]
SwiftUI’s .course of
modifier inherits its actor context from the surrounding function. Within the occasion you identify .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, within the occasion you identify .course of
from a helper property or function that isn’t @MainActor
-annotated, the async operation will run inside the cooperative thread pool.
Proper right here’s an occasion. Uncover the two .course of
modifiers in physique
and helperView
. The code is the same in every, however solely actually considered one of 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:
import SwiftUI
@MainActor func onMainActor() {
print("on MainActor")
}
struct ContentView: View {
var physique: some View {
VStack {
helperView
Textual content material("in physique")
.course of {
// We're in a position to identify a @MainActor func with out await
onMainActor()
}
}
}
var helperView: some View {
Textual content material("in helperView")
.course of {
// ❗️ Error: Expression is 'async' nonetheless is simply not marked with 'await'
onMainActor()
}
}
}
This habits is attributable to 2 (semi-)hidden annotations inside the SwiftUI framework:
-
The
View
protocol annotates itsphysique
property with@MainActor
. This transfers to all conforming types. -
View.course of
annotates itsmovement
parameter with@_inheritActorContext
, inflicting it to undertake the actor context from its use web site.
Sadly, none of these annotations are seen inside 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 operate doesn’t work reliably for me, and as we’ll see, it doesn’t current all the actuality, each.
To really 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 options. Module interfaces use common 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 could also be quite a lot of .swiftinterface
data in that itemizing, one per CPU construction. Determine any actually considered one of them. Skilled 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
// …
}
Armed with this information, each half makes further sense:
- When used inside
physique
,course of
inherits the@MainActor
context fromphysique
. - When used outdoor of
physique
, there is no such thing as a such factor as a implicit@MainActor
annotation, socourse of
will run its operation on the cooperative thread pool by default. (Till the view contains an@ObservedObject
or@StateObject
property, which someway makes the whole view@MainActor
. Nonetheless that’s a particular matter.)
The lesson: within the occasion you utilize helper properties or options in your view, ponder annotating them with @MainActor
to get the equivalent semantics as physique
.
By the best way by which, discover that the actor context solely applies to code that is positioned immediately contained within the async closure, along with to synchronous options the closure calls. Async options choose their very personal execution context, so any identify to an async function can change to a particular executor. For example, within the occasion you identify URLSession.data(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 Options for the precise pointers.
I understand Apple’s impetus to not current unofficial API or language choices inside the documentation lest builders get the preposterous thought to make use of those choices of their very personal code!
Nonetheless it makes understanding so so much harder. Sooner than I observed the annotations inside the .swiftinterface
file, the habits of the code at first of this textual content under no circumstances made sense to me. Hiding the details makes points seem like magic as soon as they really aren’t. And that’s not good, each.
[ad_2]