I'm seeing a warning when using a custom SwiftUI extension, but not when using the built-in task modifier. I'm unsure why there's a difference and how to suppress the warning without explicitly adding @MainActor to my method or using await where it's called.
Warning:
Expression is 'async' but is not marked with 'await'; this is an error in the Swift 6 language mode
Polling extension on view:
extension View {
func pollingTask(
interval: @autoclosure @escaping () -> TimeInterval,
runImmediately: Bool = true,
priority: TaskPriority = .userInitiated,
_ action: @escaping @Sendable () async -> Void
) -> some View {
self.task(priority: priority) {
repeat {
if runImmediately { await action() }
try? await Task.sleep(for: .seconds(interval()))
if !runImmediately { await action() }
} while (!Task.isCancelled)
}
}
}
Main code:
struct MyScreen: View {
@StateObject private var model = MyScreenModel()
var body: some View {
Text("Hello, World!")
.pollingTask(interval: 30) {
await model.fetch()
model.someFunction() //<------- getting warning here
}
.task {
repeat {
await model.fetch()
model.someFunction()
try? await Task.sleep(for: .seconds(30))
} while (!Task.isCancelled)
}
}
}
@MainActor
class MyScreenModel: ObservableObject {
func fetch() async {
// await network.performRequest()
}
func someFunction() {
// some task
}
}
The built-in task
is actually declared like this (from SwiftUI.swiftinterface):
@inlinable nonisolated public func task(
priority: _Concurrency.TaskPriority = .userInitiated,
@_inheritActorContext _ action: @escaping @Sendable () async -> Swift.Void
) -> some SwiftUICore.View
The crucial point is @_inheritActorContext
. This makes the action
closure isolated to the main actor when you call task
in a main actor isolated context. This attribute is underscore-prefixed, so it doesn't show up on the documentation pages.
The closure parameter for your pollingTask
does not have this attribute, so it is non-isolated. Therefore await
is needed to call a main actor isolated method like someFunction
.
While you can also add @_inheritActorContext
to your pollingTask
, I would just replace @Sendable
with @MainActor
instead. After all, unlike the built-in task
which is non-isolated, pollingTask
itself is implicitly isolated to the main actor anyway.
func pollingTask(
interval: @autoclosure @escaping () -> TimeInterval,
runImmediately: Bool = true,
priority: TaskPriority = .userInitiated,
_ action: @escaping @MainActor () async -> Void
) -> some View