Swift Concurrency Design Questions

I finally have time and the toolchains seem to be stable and featureful enough for me to start experimenting with a new core Alamofire design using the Swift concurrency features. I have a few questions and would like any general design feedback. Here's my simple program:

actor Session {
    private(set) lazy var session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
    private(set) lazy var sessionDelegate = SessionDelegate(self)
    
    private(set) var requests: [URLSessionTask: DataRequest] = [:]
    
    func request() async throws -> DataRequest {
        let request = DataRequest()
        let task = await request.task(using: session)
        requests[task] = request
        task.resume()
        
        return request
    }
    
    func request(for task: URLSessionTask) -> DataRequest? {
        requests[task]
    }
}

actor SessionDelegate: NSObject {
    private weak var owner: Session?
    
    init(_ owner: Session) {
        self.owner = owner
    }
}

extension SessionDelegate: URLSessionTaskDelegate {
    @asyncHandler
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        await owner?.request(for: task)?.didComplete(error)
    }
}

extension SessionDelegate: URLSessionDataDelegate {
    @asyncHandler
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        await owner?.request(for: dataTask)?.didReceive(data)
    }
}

actor DataRequest {
    private(set) var data = Data()
    private(set) var error: Error?
    private var completion: ((Data, Error?) -> Void)?
    
    func task(using session: URLSession) -> URLSessionTask {
        session.dataTask(with: URLRequest(url: URL(string: "https://httpbin.org/get")!))
    }
    
    func didComplete(_ error: Error?) {
        self.error = error
        print("didComplete")
        completion?(data, self.error)
    }
    
    func didReceive(_ data: Data) {
        self.data.append(data)
    }
    
    func awaitCompletion() async throws -> Data {
        try await withUnsafeThrowingContinuation { continuation in
            self.completion = { data, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            }
        }
    }
}

runAsyncAndBlock {
    let session = Session()
    do {
        let data = try await session.request().awaitCompletion()
        print(String(decoding: data, as: UTF8.self))
    } catch {
        print(error)
    }
}
  1. runAsyncAndBlock is deprecated. Is there a replacement I can use to provide an async context in a simple executable?
  2. I'm currently unable to directly set the SessionDelegate's owner from a Session initializer due to safety errors. What's the intended pattern for this sort of connection? I can see nonisolated(unsafe) eventually working, but I'd really like to know that the suggested safe solution is supposed to be.
  3. Relatedly, it seems like sharing the execution context between the Session and SessionDelegate actors would help, but that functionality doesn't seem to exist in the snapshot. Might that be an appropriate approach once we can customize the Executor's?
  4. I thought the URLSession delegate methods were supposed to marked @asyncHandler when they're imported from Obj-C. Has that not been done yet? Will it?
  5. Relatedly, I assume Apple's frameworks will be audited for ConcurrentValue conformance? This seems like a big job, is there any way the community can help?
  6. Relatedly, will the current "Cannot pass argument of non-concurrent-value..." warning be an error once that audit is complete, or is this meant to only be a warning? Adding UnsafeConcurrentValue to say, URLSession, doesn't actually fix it right now. Bug or limitation?
  7. I'd like my DataRequest type to offer an async completion API, but there doesn't seem to be a great way to trigger such an event. My current approach of setting a closure which resumes an UnsafeContinuation seems to work, but surely that can't be the recommended approach. Is there a feature planned to make creating APIs similar to Task.Handle easier?
  8. This program completes the network request but then crashes. Reported as SR-14283.
  9. Any other suggestions?
7 Likes
Terms of Service

Privacy Policy

Cookie Policy