Search code examples
swiftswiftuiswift-concurrency

Complete Concurrency check enabled and how to resolve warnings


I am looking to resolve the concurrency warnings that I am getting after enabling the complete concurrency check in Xcode.

Here is a simple example to illustrate the warning. Consider I have a ContentView that has a long running task on the background thread. When I call the long running task inside that of a Task {...} I get a "Passing argument of non-sendable type 'ContentView' outside of main actor-isolated context may introduce data races" warning. I get this warning when I enable the concurrency settings to complete in Xcode. I tried the suggestion to make the ContentView Sendable but it generates additional warnings(see second image below) and will not work in my situation. How do I get around this? appreciate any help/direction

See code below:

struct ContentView: View {
    @State private var disable: Bool = false
    @State private var value: Double = 0

    var body: some View {
        VStack {
            Button {
                disable = true
            } label: {
                Text("Press")
            }
            .task {
                await value = self.fetchValue() //<---Here is the warning. See image below
            }
            Text("\(value)")

        }
    }
}

extension ContentView {
    
    func fetchValue() async -> Double {
        //long running task
        try? await Task.sleep(nanoseconds: 60)
        return 2.0
    }
}

Here is the warning:

enter image description here

enter image description here


Solution

  • A SwiftUI View is not attached to an actor the body is decorated with @MainActor but not the View itself.

    When you have func in the View you can decorate the View with @MainActor the same way you would a "ViewModel".

    import SwiftUI
    @MainActor // Decorate the View
    struct AsyncTest: View {
        @State private var isRunning: Bool = false
        @State private var value: Double = 0
    
        var body: some View {
            VStack {
                Button {
                    print("press")
                    isRunning = true
                } label: {
                    Text("Press")
                }.disabled(isRunning)
                    .task(id: isRunning) {
                        guard isRunning else {return}
                        print("start")
                        value = await fetchValue()
                        print("end")
                        isRunning = false
                    }
                if isRunning {
                    ProgressView()
                }
                
                Text("\(value)")
    
            }
        }
    }
    
    extension AsyncTest {
        
        func fetchValue() async -> Double {
            await Task.detached(priority: .userInitiated) {
                // Mock long runing task
                _ = (0...10000000).map { n in
                    n.description
                }.sorted(using: KeyPathComparator(\.description))
                //long running task
                //try? await Task.sleep(nanoseconds: 60)
                return 2.0
            }.value
        }
    }
    #Preview {
        AsyncTest()
    }