How to make non-sendable type usable in Sendable context?

450 Views Asked by At

I have such code

public func webView(
        _ webView: WKWebView,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {

        
        Task.detached(priority: .background) {
 guard let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
            let exceptions = SecTrustCopyExceptions(serverTrust)
            SecTrustSetExceptions(serverTrust, exceptions)
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        }
    }

But Xcode complains that URLAuthenticationChallenge type is non-Sendable How can I make this type sendable, or pass it to task and provide multithread safe access

I've done something like this

struct SendableValue<T> {
    let value: T
}

Warning disappeared but I daubt that it is correct solution as there isn't any mutexes

Maybe it should be more like this

struct SendableValue<T>: Sendable {
    private let semaphore = DispatchSemaphore(value: 1)
    private var _value: T
    
    init(value: T) {
        _value = value
    }
    
    var value: T {
        get {
            semaphore.wait()
            defer { semaphore.signal() }
            return _value
        }
        set {
            semaphore.wait()
            defer { semaphore.signal() }
            _value = newValue
        }
    }
}

but adding conformance to Sendable on this type throws another warnings

maybe I shouldn't conform to Sendable

I think I am doing somehting wrong here.

Is there any generic simple way to provide isolation layer to non-Sendable type from third party library to make it Sendable i.e. safe in multiconcurrency environment?

1

There are 1 best solutions below

0
On

I think you can try @unchecked, and warp challenge.protectionSpace.serverTrust.

struct SendableURLAuthenticationChallenge: @unchecked Sendable {
    private let semaphore = DispatchSemaphore(value: 1)
    private var challenge: URLAuthenticationChallenge
    
    init(value: URLAuthenticationChallenge) {
        challenge = value
    }
    
    var trust: SecTrust? {
        semaphore.wait()
        defer { semaphore.signal() }
        return challenge.protectionSpace.serverTrust
    }
}


public func webView(
        _ webView: WKWebView,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {

        let sendabelChallenge = SendableURLAuthenticationChallenge(value: challenge)
        
        Task.detached(priority: .background) {
            guard let serverTrust = sendabelChallenge.trust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
            let exceptions = SecTrustCopyExceptions(serverTrust)
            SecTrustSetExceptions(serverTrust, exceptions)
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        }
    }

but using synchronization mechanisms in asynchronous functions is indeed dangerous and requires careful testing