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