swiftxcodeswiftuiswift-concurrency

Getting warning if is use custom extension for swiftui task on xcode 16.4 but wonder why i'm not getting it when i use built-in task


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
    }
}

Warnning


Solution

  • 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