Hello fellow nerds,
I came across this very annoying compilation error after upgrading to Xcode 26.0 and Swift 6.2.
Say you have this actor:
import Foundation
@globalActor
actor MyActor: GlobalActor {
static let shared: MyActor = .init()
}
Now say you want to implement a subclass of URLProtocol
just to mock some network calls.
Example 1:
final class NoSendableURLProtocol: URLProtocol {
final override func startLoading() {
Task { @MyActor [weak self] in
guard let self else { return } // error: Sending 'self' risks causing data races
}
}
}
Example 2:
@MyActor
final class ActorURLProtocol: URLProtocol {
final override func startLoading() {
Task { @MyActor [weak self] in
guard let self else { return } // error: Sending 'self' risks causing data races
}
}
}
Example 3:
@preconcurrency
final class PreconcurrencyURLProtocol: URLProtocol {
final override func startLoading() {
Task { @MyActor [weak self] in
guard let self else { return } // error: Sending 'self' risks causing data races
}
}
}
Example 4:
final class SendableURLProtocol: URLProtocol, Sendable { // 'Sendable' class 'SendableURLProtocol' cannot inherit from another class other than 'NSObject'
final override func startLoading() {
Task { @MyActor [weak self] in
guard let self else { return } // error: same as before
}
}
}
Example 5:
@preconcurrency
final class UncheckedSendableURLProtocol: URLProtocol, @unchecked Sendable { // Conformance of 'UncheckedSendableURLProtocol' to protocol 'Sendable' is already unavailable
final override func startLoading() {
Task { @MyActor [weak self] in
guard let self else { return } // error: same as before
}
}
}
Example 6:
final class WorkingURLProtocol: URLProtocol, @unchecked Sendable {
final override func startLoading() {
Task { @MyActor [request = task?.originalRequest, requestURL = task?.originalRequest?.url, client] in
guard let request, let requestURL else {
fatalError()
}
defer {
client?.urlProtocolDidFinishLoading(self)
}
// A whole lot of code, so that the compiler "forgets" to check that `self` is being sent
client?.urlProtocol(self, didLoad: .init())
}
}
}
In a Playground, I can't get this to work actually, but I can in a project of mine. Can't share the code unfortunately, but I guarantee you the compiler "stops caring" at one point.
Example #4 above led me to this post, which doesn’t really address the issue.
It seems to me that subclasses of NSObject
that are themselves not final
cause the sendable checks to go nuts. So a subclass of a subclass (ie. MyProtocol: URLProtocol: NSObject
) does not play well with Sendable
.
TLDR
What is a bug in my opinion though, is that marking it as @unchecked Sendable
does nothing, neither does @preconcurrency
.
Extra bits:
- Xcode Version 26.0 (17A324)
swift-driver version: 1.127.14.1 Apple Swift version 6.2 (swiftlang-6.2.0.19.9 clang-1700.3.19.1)