Search code examples
iosswiftswiftuiconcurrencyswift-concurrency

Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races


I am using SwiftUI and I declared the View as @MainActor to resolve some of the concurrency warnings. However, I still get the warning

Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races

on

let (data, _) = try await URLSession.shared.data(from: svgURL)

I am not passing any URLSessionTaskDelegate at all.

I tried looking at the docs of URLSession.shared.data and git this

 /// Convenience method to load data using a URL, creates and resumes a URLSessionDataTask internally.
    ///
    /// - Parameter url: The URL for which to load data.
    /// - Parameter delegate: Task-specific delegate.
    /// - Returns: Data and response.
    public func data(from url: URL, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse)

I don't get it i tried passing nil but the error is still not going away. what am I doing wrong?

for more context here's a simplified code

@MainActor
struct EntryIcon: View {
    private func loadImage() {
        ....
            if let svgURL = URL(string: urlString) {
                Task {
                    do {
                        // WARNING: Passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races
                        let (data, _) = try await URLSession.shared.data(from: svgURL)
                        let uiImage = SVG(data: data)?.rasterize().withRenderingMode(.alwaysTemplate).withTintColor(UIColor(color))
                        if let uiImage {
                            image = Image(uiImage: uiImage)
                        }
                    } catch {
                        logger.error("error \(error)")
                    }
                }
            }
        ...
    }
}

I also tried putting

Task { @MainActor in

but the warning is still there


Solution

  • The warning is talking about the delegate parameter of data(from:delegate:). This parameter has a default value (nil) so it is still passed, even if you are not passing anything explicitly.

    The Task is running in a main actor-isolated context, but data(from:delegate:) is not isolated to the main actor, hence you are passing the delegate from a main actor-isolated context, to a non-isolated context. "Outside of main actor-isolated context" as the warning says.

    So you should call data(from:delegate:) from a non-isolated context in the first place. A simple way to do that is to create a wrapper of data(from:delegate) which does not have a delegate parameter.

    extension URLSession {
        func data(from url: URL) async throws -> (Data, URLResponse) {
            // this call is in a non-isolated context, so all is good :)
            try await URLSession.shared.data(from: url, delegate: nil)
        }
    }
    

    After that, the call in your Task { ... } will automatically resolve to the overload above. You are still calling a non-isolated function from a main actor-isolated context, but this time, you are no longer passing a URLSessionTaskDelegate (and URL is Sendable), so there are no warnings.


    Side note: in a View, consider using the task and task(id:) modifiers instead of creating a top level Task { ... }. These modifiers cancels the task automatically when the view disappears.