I have an API class in which I am creating a URL session data task that updates the value of a birdResult property in the class when the task completes from within a DispatchQueue.main.async block.
In Swift 6, the following error appears:
My class code looks like this:
class BirdsAPI: ObservableObject {
static let url = URL(string: "https://api.inaturalist.org/v1/observations?iconic_taxa=Aves&per_page=50&order=desc&order_by=created_at")
@Published var birdResults: [BirdResult] = []
func fetchObservations() {
URLSession.shared.dataTask(with: URLRequest(url: BirdsAPI.url!)) { data, response, error in
guard error == nil else { return }
let decoder = JSONDecoder()
do {
let body = try decoder.decode(Body.self, from: data!)
DispatchQueue.main.async {
self.birdResults = body.results
}
} catch let error {
print("error decoding data, \(error)")
}
}.resume()
}
}
Why is this and how can I fix it?
@Senable describes the types that are safe to share concurrently. i.e:
struct A: Senable { } //✅
class B: Senable { } //❌ unsynchronized
final class C: Senable { //❌ mutating property
var birdResults: [BirdResult] = []
}
final class D: Senable { //✅
let birdResults: [BirdResult]
}
The purpose is to avoid creating the risk of data races. In your example, your BirdsAPI
class does not conform to Senable
, but the completionHandler is marked with @Sendable
:
func dataTask( with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void ) -> URLSessionDataTask
Although you may disregard the error from Swift 6 using @unchecked
. You should not do this unless you have some internal locking mechanism in place when modifying the property.
class BirdsAPI: ObservableObject, @unchecked Sendable { } //<- here
Thus, I recommend refactoring the code to the following and avoiding combining the DispatchQueue
family with the new concurrency async await
.
@MainActor
func fetchObservations() async throws {
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: BirdsAPI.url!))
let decoder = JSONDecoder()
let body = try decoder.decode(Body.self, from: data!)
self.birdResults = body.results
}