Output from swift --version with strict checking enabled etc.:
swift-driver version: 1.115 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)
Is someone able to explain why lines marked A1 and C1 are not allowed? I don't understand why moving them into an explicit Task changes anything. Am I missing something?
import SwiftUI
import Observation
struct SendRequestView: View {
@Bindable var config: Config
var body: some View {
Text("Hello")
.task { await sendRequest(config: config) }
}
// This is MainActor
private func sendRequest(config: Config) async {
// let apiResponse = try? await NetworkAPI.sendRequest(config: config) // A1. Not allowed.
let apiResponseIsolated = try? await NetworkAPI.sendRequestIsolated(config: config) // B1. Allowed.
// let apiResponseNonIsolated = try? await NetworkAPI.sendRequestNonIsolated(config: config) // C1. Not allowed.
// A1 and C1 error
// Sending 'config' risks causing data races
// Sending main actor-isolated 'config' to nonisolated static method 'sendRequestNonIsolated(config:)' risks causing data races between nonisolated and main actor-isolated uses
// Sending main actor-isolated 'config' to nonisolated static method 'sendRequest(config:)' risks causing data races between nonisolated and main actor-isolated uses
Task {
// This is MainActor
let apiResponse = try? await NetworkAPI.sendRequest(config: config) // A2. Allowed.
let apiResponseIsolated = try? await NetworkAPI.sendRequestIsolated(config: config) // B2. Allowed.
let apiResponseNonIsolated = try? await NetworkAPI.sendRequestNonIsolated(config: config) // C2. Allowed.
}
// ...
}
}
enum NetworkAPI {
static func sendRequest(config: Config) async throws -> APIResponse {
let urlRequest = URLRequest(url: config.url)
let urlSession = URLSession(configuration: .ephemeral)
let response = try await urlSession.data(for: urlRequest)
return APIResponse(response: response)
}
static func sendRequestIsolated(config: Config, isolation: isolated (any Actor)? = #isolation) async throws -> APIResponse {
let urlRequest = URLRequest(url: config.url)
let urlSession = URLSession(configuration: .ephemeral)
let response = try await urlSession.data(for: urlRequest)
return APIResponse(response: response)
}
nonisolated static func sendRequestNonIsolated(config: Config) async throws -> APIResponse {
let urlRequest = URLRequest(url: config.url)
let urlSession = URLSession(configuration: .ephemeral)
let response = try await urlSession.data(for: urlRequest)
return APIResponse(response: response)
}
}
@Observable
final class Config: Identifiable, Codable {
var id: UUID = UUID()
var url: URL
init(url: URL) { self.url = url }
}
struct APIResponse {
let data: Data
let urlResponse: URLResponse
init(response: (Data, URLResponse)) {
data = response.0
urlResponse = response.1
}
}